From: Michael McMaster Date: Fri, 14 Oct 2011 20:46:50 +0000 (+1000) Subject: Initial android port X-Git-Url: http://git.codesrc.com/gitweb.cgi?a=commitdiff_plain;h=76af0a232e19bf91890617e7007ab2a2e2352d0e;p=glBoy.git Initial android port --- diff --git a/ALU.cc b/ALU.cc deleted file mode 100644 index b8291f5..0000000 --- a/ALU.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2011 Michael McMaster -// -// This file is part of glBoy. -// -// glBoy is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// glBoy is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with glBoy. If not, see . - -#include "ALU.hh" - -using namespace glBoy; - -void ALU::load() {} - -const uint8_t ALU::ParityFlagLookup[256] = -{ -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, -0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04 -}; diff --git a/ALU.hh b/ALU.hh index d71a1e6..fa01321 100644 --- a/ALU.hh +++ b/ALU.hh @@ -26,33 +26,16 @@ namespace glBoy { namespace ALU { -void load(); -extern const uint8_t ParityFlagLookup[256]; - inline uint8_t add(uint8_t a, uint8_t b, Flags& flags, bool carry = false) { uint8_t c = (carry && flags.C) ? 1 : 0; uint8_t result(a + b + c); - flags.byte = - (result & 0x80) | // S MSB - ((result == 0) ? 0x40 : 0) | // Z - // U5 TODO - ((a ^ b ^ result) & 0x10) | // H - // U3 TODO - - // If a and b have the same sign, and the result - // has a different sign, then we overflowed the - // register. - // Can never overflow if a and b have different signs. (even with - // carry, since 127 + -1 + carry == 127). - // Step 1) Determine whether a and b have the same sign. (a NXOR b) - // Step 2) Determine whether the result has // a different sign. - ((((~(a ^ b)) & (result ^ a)) & 0x80) >> 5) |// P - - // N - (((uint16_t(a) + uint16_t(b) + c) & 0x100) >> 8); // C + flags.C = ((uint16_t(a) + uint16_t(b) + c) & 0x100) ? 1 : 0; + flags.H = ((a ^ b ^ result) & 0x10) ? 1 : 0; + flags.N = 0; + flags.Z = (result == 0) ? 1 : 0; return result; } @@ -75,10 +58,7 @@ add(DWORD a, DWORD b, Flags& flags, bool carry = false) if (carry) { // Flags only affected for adc. - flags.S = (result & 0x8000) ? 1 : 0; flags.Z = (result == 0); - flags.P = (((~(aHost ^ bHost)) & (result ^ aHost)) & 0x8000) ? 1 : 0; - } flags.N = 0; @@ -88,6 +68,21 @@ add(DWORD a, DWORD b, Flags& flags, bool carry = false) return glBoy::htoz(result); } +inline DWORD +add(DWORD a, int8_t b, Flags& flags) +{ + const uint16_t aHost(a.host()); + + uint16_t result(aHost + b); + + flags.C = (aHost & 0xFF) + uint8_t(b) > 0xFF ? 1 : 0; + flags.N = 0; + flags.H = ((aHost ^ b ^ result) & 0x10) ? 1 : 0; + flags.Z = 0; + + return glBoy::htoz(result); +} + inline DWORD adc(DWORD a, DWORD b, Flags& flags) { @@ -100,22 +95,10 @@ sub(uint8_t a, uint8_t b, Flags& flags, bool borrow = false) uint8_t c = (borrow && flags.C) ? 1 : 0; uint8_t result(a - b - c); - flags.byte = - (result & 0x80) | // S MSB - ((result == 0) ? 0x40 : 0) | // Z - // U5 TODO - (((a & 0xf) - (b & 0xf)) & 0x10) | // H - // U3 TODO - - // Detected signed overflow - ((int16_t(static_cast(a)) - - int16_t(static_cast(b)) - - c - ) == - static_cast(result) ? 0 : 0x4) | // P - - 0x2 | // N - (((uint16_t(a) - uint16_t(b) - c) & 0x100) >> 8); // C + flags.C = ((uint16_t(a) - uint16_t(b) - c) & 0x100) ? 1 : 0; + flags.H = (((a & 0xf) - (b & 0xf) - c) & 0x10) ? 1 : 0; + flags.N = 1; + flags.Z = (result == 0) ? 1 : 0; return result; } @@ -135,22 +118,10 @@ sub(DWORD a, DWORD b, Flags& flags, bool borrow = false) uint16_t c = (borrow && flags.C) ? 1 : 0; uint16_t result(aHost - bHost - c); - flags.byte = - ((result & 0x8000) >> 8) | // S MSB - ((result == 0) ? 0x40 : 0) | // Z - // U5 TODO - ((((aHost & 0xf00) - (bHost & 0xf00)) & 0x1000) >> 8) | // H - // U3 TODO - - // Detected signed overflow - ((int32_t(static_cast(aHost)) - - int32_t(static_cast(bHost)) - - c - ) == - static_cast(result) ? 0 : 0x4) | // P - - 0x2 | // N - (((uint32_t(aHost) - uint32_t(bHost) - c) & 0x10000) >> 16); // C + flags.C = (((uint32_t(aHost) - uint32_t(bHost) - c) & 0x10000)) ? 1 : 0; + flags.H = (((aHost & 0xf00) - (bHost & 0xf00)) & 0x1000) ? 1 : 0; + flags.N = 1; + flags.Z = (result == 0) ? 1 : 0; return htoz(result); } @@ -165,15 +136,12 @@ inline uint8_t bitwiseAnd(uint8_t a, uint8_t b, Flags& flags) { uint8_t result(a & b); - flags.byte = - (result & 0x80) | // S - ((result == 0) ? 0x40 : 0) | // Z - // U5 - 0x10 | // H - // U3 - ParityFlagLookup[result]; // P - // N - // C + + flags.C = 0; + flags.H = 1; + flags.N = 0; + flags.Z = (result == 0) ? 1 : 0; + return result; } @@ -181,15 +149,11 @@ inline uint8_t bitwiseOr(uint8_t a, uint8_t b, Flags& flags) { uint8_t result(a | b); - flags.byte = - (result & 0x80) | // S - ((result == 0) ? 0x40 : 0) | // Z - // U5 - // H - // U3 - ParityFlagLookup[result]; // P - // N - // C + + flags.C = 0; + flags.H = 0; + flags.N = 0; + flags.Z = (result == 0) ? 1 : 0; return result; } @@ -197,15 +161,11 @@ inline uint8_t bitwiseXor(uint8_t a, uint8_t b, Flags& flags) { uint8_t result(a ^ b); - flags.byte = - (result & 0x80) | // S - ((result == 0) ? 0x40 : 0) | // Z - // U5 - // H - // U3 - ParityFlagLookup[result]; // P - // N - // C + + flags.C = 0; + flags.H = 0; + flags.N = 0; + flags.Z = (result == 0) ? 1 : 0; return result; } @@ -226,14 +186,14 @@ rotateLeft(uint8_t a, Flags& flags, bool throughCarry, bool setAllFlags = false) flags.H = 0; flags.N = 0; flags.C = msb; - if (setAllFlags) { - flags.byte |= (a & 0x80); flags.Z = (a == 0) ? 1 : 0; - flags.byte |= ParityFlagLookup[a]; } -// TODO U3 U5 + else + { + flags.Z = 0; + } return a; } @@ -244,15 +204,10 @@ shiftLeft(uint8_t a, Flags& flags) uint8_t msb(a >> 7); a <<= 1; - flags.byte = - (a & 0x80) | // S - ((a == 0) ? 0x40 : 0) | // Z - // U5 - // H - // U3 - ParityFlagLookup[a] | // P - // N - msb; // C + flags.C = msb; + flags.H = 0; + flags.N = 0; + flags.Z = (a == 0) ? 1 : 0; return a; } @@ -277,11 +232,12 @@ rotateRight(uint8_t a, Flags& flags, bool throughCarry, bool setAllFlags = false if (setAllFlags) { - flags.byte |= (a & 0x80); flags.Z = (a == 0) ? 1 : 0; - flags.byte |= ParityFlagLookup[a]; } -// TODO U3 U5 + else + { + flags.Z = 0; + } return a; } @@ -296,21 +252,16 @@ shiftRight(uint8_t a, Flags& flags, bool arithmatic) // Restore sign bit if (arithmatic) a |= msb; - flags.byte = - (a & 0x80) | // S - ((a == 0) ? 0x40 : 0) | // Z - // U5 - // H - // U3 - ParityFlagLookup[a] | // P - // N - lsb; // C + flags.C = lsb; + flags.H = 0; + flags.N = 0; + flags.Z = (a == 0) ? 1 : 0; return a; } inline void -RLD(uint8_t& a, uint8_t& m, Flags& f) +RLD(uint8_t& a, uint8_t& m, Flags& flags) { // Whoever came up with RLD was on crack. @@ -318,19 +269,14 @@ RLD(uint8_t& a, uint8_t& m, Flags& f) a = (a & 0xF0) | (m >> 4); m = newM; - f.byte = - (a & 0x80) | // S - ((a == 0) ? 0x40 : 0) | // Z - // U5 - // H - // U3 - ParityFlagLookup[a]; // P - // N - // C + flags.C = 0; + flags.H = 0; + flags.N = 0; + flags.Z = (a == 0) ? 1 : 0; } inline void -RRD(uint8_t& a, uint8_t& m, Flags& f) +RRD(uint8_t& a, uint8_t& m, Flags& flags) { // Whoever came up with RRD was on crack. @@ -338,15 +284,10 @@ RRD(uint8_t& a, uint8_t& m, Flags& f) a = (a & 0xF0) | (m & 0xf); m = newM; - f.byte = - (a & 0x80) | // S - ((a == 0) ? 0x40 : 0) | // Z - // U5 - // H - // U3 - ParityFlagLookup[a]; // P - // N - // C + flags.C = 0; + flags.H = 0; + flags.N = 0; + flags.Z = (a == 0) ? 1 : 0; } } // namespace ALU diff --git a/Config.cc b/Config.cc deleted file mode 100644 index 3ced34d..0000000 --- a/Config.cc +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (C) 2011 Michael McMaster -// -// This file is part of glBoy. -// -// glBoy is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// glBoy is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with glBoy. If not, see . - - -#include "glBoy.hh" -#include "Core.hh" -#include "GameboyCart.hh" -#include "GameboyGraphics.hh" -#include "GameboyJoypad.hh" -#include "GameboySound.hh" -#include "GameboyTimer.hh" -#include "RAM.hh" -#include "ROM.hh" -#include "Log.hh" - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace -{ - SDL_Surface* - initOpenGL(double xScale, double yScale, bool fullscreen) - { - glBoy::Log::Instance()(glBoy::Log::DBG_STAT) << "Enabling OpenGL" << std::endl; - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - unsigned int options(SDL_OPENGL); - if (fullscreen) - { - options |= SDL_FULLSCREEN; - } - - SDL_Surface* surface( - SDL_SetVideoMode(160*xScale, 144*yScale, 32, options) - ); - - glEnable(GL_TEXTURE_2D); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0f, 160, 144, 0.0f, -1.0f, 1.0f); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - return surface; - } - - void usage(const std::string& exe) - { - std::cout << - "Usage: " << exe << " " - "[--scale ratio] [--width w] [--height h] \n" - //"\t\t [--resample-method={bilinear|nearest} \n" - //"\t\t[--scaling-method={hq4x|scale2x|none}] " - "rom.gb" << std::endl << - std::endl << - "\t--scale\t(Default=4.0)" << std::endl << - "\t\tMultiplier applied to the display output dimensions." << std::endl << - "\t--width" << std::endl << - "\t\tSet the display output dimensions directly" << std::endl << - "\t--height" << std::endl << - "\t\tSet the display output dimensions directly" << std::endl; - /*"\t--scaling-method\n\t\t\t(Default=\"hq4x\")" << std::endl << - "\t\tSets the upscaling interpolation method." << std::endl << - "\t--resample-method\n\t\t\t(Default=\"bilinear\")" << std::endl << - "\t\tSets the resampling method." << std::endl;*/ - } -} - -int main(int argc, char** argv) -{ - static struct option longOptions[] = - { - {"video", 1, NULL, 'v'}, - {"scale", 1, NULL, 's'}, - {"width", 1, NULL, 'w'}, - {"height", 1, NULL, 'h'}, - //{"resample-method", 1, NULL, 'r'}, - //{"scaling-method", 1, NULL, 'm'}, - {NULL, 0, NULL, 0} - }; - - std::string filename("tetris.gb"); - glBoy::GameboyGraphics::PixelScaler scaler(glBoy::GameboyGraphics::Scaler_hq4x); - glBoy::GameboyGraphics::ResampleMethod resampler(glBoy::GameboyGraphics::Resample_Bilinear); - bool fullscreen(false); - double scaleX(4.0); - double scaleY(4.0); - - int optionIndex(0); - int c; - while ( - (c = getopt_long(argc, argv, "s:w:h:", &longOptions[0], &optionIndex)) - != -1) - { - switch (c) - { - case 'h': - { - int height(160); - std::stringstream convert(optarg); - convert >> height; - if (convert && height > 14 && height < 1440) - { - scaleY = height / 144.0; - } - }; break; - case 'r': - { - if (std::string(optarg) == "bilinear") - { - resampler = glBoy::GameboyGraphics::Resample_Bilinear; - } - else - { - resampler = glBoy::GameboyGraphics::Resample_Nearest; - } - }; break; - case 's': - { - double scale(1); - std::stringstream convert(optarg); - convert >> scale; - if (convert && scale > 0.1 && scale < 10) - { - scaleX = scale; - scaleY = scale; - } - }; break; - case 'm': - { - if (std::string(optarg) == "hq4x") - { - scaler = glBoy::GameboyGraphics::Scaler_hq4x; - } - else if (std::string(optarg) == "scale2x") - { - scaler = glBoy::GameboyGraphics::Scaler_Scale2x; - } - else - { - scaler = glBoy::GameboyGraphics::Scaler_None; - } - }; break; - case 'w': - { - int width(160); - std::stringstream convert(optarg); - convert >> width; - if (convert && width > 16 && width < 1600) - { - scaleX = width / 160.0; - } - }; break; - - case '?': - usage(argv[0]); - exit(1); - break; - - default: - abort(); - }; - } - - if (optind < argc) - { - filename = argv[optind++]; - } - - if (access(filename.c_str(), R_OK) != 0) - { - char* error(strerror(errno)); - std::cerr << "Could not read file " << filename << - "(" << error << ")" << std::endl; - exit(1); - } - - glBoy::Core core; - glBoy::MemoryMap& map(core.getMemoryMap()); - - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO); - - SDL_Surface* surface(initOpenGL(scaleX, scaleY, fullscreen)); - - glBoy::GameboyGraphics graphics(core, surface, scaler, resampler); - - // Map the cart memory. - int fd(open(filename.c_str(), O_RDONLY)); - assert(fd >= 0); - - struct stat buf; - if (fstat(fd, &buf)) - { - assert(!"Failed fstat"); - } - uint8_t* cart = new uint8_t[buf.st_size]; - read(fd, cart, buf.st_size); - close(fd); - - glBoy::GameboyCart tmp(core, cart, buf.st_size); - - -/* - int biosFd(open("DMG_ROM.bin", O_RDONLY)); - uint8_t bios[256]; - read(biosFd, bios, 256); - std::shared_ptr biosMem(new glBoy::ROM(bios, 256)); - map.map(0, 256, biosMem); -*/ - - // Map the cart mem. - // Map the standard internal RAM, plus shadow. - { - std::shared_ptr mem(new glBoy::RAM(8192)); - map.map(0xC000, 8192, mem); - map.map(0xE000, 7680, mem); - } - // Map the "zero-page" high-speed internal ram. - // Most interaction between GB hardware and the program occurs here. - { - std::shared_ptr mem(new glBoy::RAM(128)); - map.map(0xFF80, 128, mem); - } - - // Map the Interrupt flags. Currently handled in Core.cc - { - std::shared_ptr mem(new glBoy::RAM(1)); - mem->write8(0, 0); - map.map(0xFF0F, 1, mem); - } - - // Map other devices - std::shared_ptr joypad(new glBoy::GameboyJoypad(core)); - map.map(0xFF00, 1, joypad); - std::shared_ptr timer( - new glBoy::GameboyTimer( - core, - std::bind(&glBoy::GameboyGraphics::setFrameSkip, &graphics), - std::bind(&glBoy::GameboyGraphics::clearFrameSkip, &graphics) - ) - ); - map.map(0xFF04, 4, timer); - std::shared_ptr sound(new glBoy::GameboySound(core)); - map.map(0xFF10, 48, sound); - - // Cart starts from offset 0x100 with a NOP; JP - core.getRegisters().PC = glBoy::htoz(0x100); - core.getRegisters().SP = glBoy::htoz(0xFFFE); - core.getRegisters().AF = glBoy::htoz(0x01B0); - core.getRegisters().BC = glBoy::htoz(0x0013); - core.getRegisters().DE = glBoy::htoz(0x00D8); - core.getRegisters().HL = glBoy::htoz(0x014D); - - core.run(); - - std::cerr << "Done" << std::endl; - - SDL_FreeSurface(surface); - SDL_Quit(); -} - diff --git a/Core.cc b/Core.cc index 8daf450..493a3b5 100644 --- a/Core.cc +++ b/Core.cc @@ -22,40 +22,56 @@ #include "Log.hh" #include "util.hh" +#include "RAM.hh" + #include #include #include #include -#include // ffs +#include // ffs using namespace glBoy; +namespace +{ + class MappedRegister : public MemoryMap::Memory + { + public: + MappedRegister(uint8_t* reg) : m_reg(reg) {} + virtual uint8_t read8(uint16_t) { return *m_reg; } + virtual void write8(uint16_t, uint8_t value) { *m_reg = value; } + + private: + uint8_t* m_reg; // Points to member variable of Core class. + }; +} + Core::Core() : - m_iff1(0), - m_iff2(0), - m_pendingInterrupts(0), + m_running(true), + m_ime(0), + m_ie(0), + m_if(0), m_halted(false), m_clock(0), m_nextPeriodicCallback(std::numeric_limits::max()) { m_reg.reset(); - ALU::load(); + + std::shared_ptr ifMemory( + new MappedRegister(&m_if)); + m_mem.map(0xFF0F, 1, ifMemory); + + std::shared_ptr ieMemory( + new MappedRegister(&m_ie)); + m_mem.map(0xFFFF, 1, ieMemory); } void -Core::raiseInterrupt(int interrupt) +Core::raiseInterrupt(uint8_t interrupt) { // TODO this interrupt handling is GB80 specific - - switch (interrupt) - { - case 0x40: m_pendingInterrupts |= 0x1; break; - case 0x48: m_pendingInterrupts |= 0x2; break; - case 0x50: m_pendingInterrupts |= 0x4; break; - case 0x58: m_pendingInterrupts |= 0x8; break; - case 0x60: m_pendingInterrupts |= 0x10; break; - } + m_if = (m_if | (uint8_t(1) << ((interrupt - 0x40) / 8))) & m_ie; } void @@ -69,50 +85,54 @@ Core::run() // handler // TODO z80 only. Unused for gb80 uint8_t doublePrefixOperand(0); - while (true) +#ifdef GLBOY_DEBUG + bool traceCPU(Log::Instance().isDebugLevelOn(Log::DBG_TRACE_CPU)); +#endif + while (m_running) { cycle_t clock = 0; uint16_t prefix(0); // Used for debug logging only. // Check for interrupts. - if (m_pendingInterrupts) + if (m_if) { // Only call ONE interrupt, even if multiple triggered. - uint8_t priorityBit(1 << (ffs(m_pendingInterrupts) - 1)); - - // We clear the IF of the interrupt we end up calling. - // BUT if there are 2 simulataneous interrupts, it is left - // with bits set, but never called. - uint8_t iFlag(m_mem.read8(htoz(0xFF0F))); - iFlag |= m_pendingInterrupts; - m_pendingInterrupts = 0; - - uint8_t ie(m_mem.read8(htoz(0xFFFF))); - - if (m_iff1 && (ie & priorityBit)) + if (m_ime) { - // Clear the IF of ONLY the interrupt we end up calling. - iFlag ^= priorityBit; + // We clear the IF of the interrupt we end up calling. + // BUT if there are 2 simulataneous interrupts, it is left + // with bits set - m_iff1 = 0; - m_iff2 = 0; + uint8_t priorityBit(ffs(m_if) - 1); + m_if ^= (uint8_t(1) << priorityBit); + m_ime = 0; m_reg.SP.dec(2); m_mem.write16(m_reg.SP, m_reg.PC); - static const int addr[] = {0x40, 0x48, 0x50, 0x58, 0x60}; - m_reg.PC = htoz(addr[ffs(priorityBit) - 1]); - - m_halted = false; + m_reg.PC = htoz((priorityBit * 8) + 0x40); } - m_mem.write8(htoz(0xFF0F), iFlag); + m_halted = false; // Interrupts always recover from HALT } - else if (!m_halted) + + if (!m_halted) { - uint16_t opcodePC = m_reg.PC.host(); // For debugging uint8_t opcode = op8(); +#ifdef GLBOY_DEBUG + DWORD opcodePC = m_reg.PC; // For debugging + + std::stringstream ss; + std::ostream& dbg(traceCPU ? ss : Log::Instance().nullStream()); + + if (m_mem.getBankSize(m_reg.PC)) + { + dbg << hex(m_mem.getBankIndex(opcodePC)) << "."; + } + dbg << opcodePC << " "; +#endif + goto *(OpcodeTable[opcode]); #include "InstructionSet_Opcodes.cc" @@ -120,11 +140,14 @@ Core::run() BAD_OPCODE: Err() << "Unknown Opcode: " << glBoy::hex(opcode) << - " (PC=" << opcodePC << ")" << + " (PC=" << m_reg.PC << ")" << Log::endl; assert(false); END_INSTRUCTIONS: ; +#ifdef GLBOY_DEBUG + TraceCPU() << ss.str() << " A=" << hex(m_reg.A) << Log::endl; +#endif } else { @@ -134,7 +157,7 @@ END_INSTRUCTIONS: ; m_clock += clock; - if (m_halted && (m_pendingInterrupts == 0)) + if (m_halted && (m_if == 0)) { m_clock = std::max(m_clock, m_nextPeriodicCallback); } @@ -162,6 +185,7 @@ END_INSTRUCTIONS: ; } } } + } Core::CallbackId diff --git a/Core.hh b/Core.hh index a2b10b0..e3f3a13 100644 --- a/Core.hh +++ b/Core.hh @@ -40,12 +40,13 @@ public: Registers& getRegisters() { return m_reg; } void run(); + void resume() { m_running = true; } + void stop() { m_running = false; } + bool isRunning() const { return m_running; } cycle_t getClockCount() const { return m_clock; } - bit getIFF2() const { return m_iff2; } - - void raiseInterrupt(int interrupt); + void raiseInterrupt(uint8_t interrupt); void halt() { m_halted = true; } typedef int CallbackId; @@ -59,17 +60,18 @@ private: uint8_t op8(); DWORD op16(); - // The Z80 can switch between register sets. + bool m_running; + Registers m_reg; - Registers m_regDash; - // Interrupt enablement - bit m_iff1; - bit m_iff2; - uint8_t m_pendingInterrupts; + // Interrupt master enable + bit m_ime; + + // Interrupt enable - enables/disables specific interrupts + uint8_t m_ie; - enum InterruptMode { IM_0, IM_1, IM_2 }; - InterruptMode m_interruptMode; + // Interrupt flag - pending interrupts + uint8_t m_if; bool m_halted; diff --git a/DAA.hh b/DAA.hh index 6e1c3bf..d0d5cc9 100644 --- a/DAA.hh +++ b/DAA.hh @@ -42,17 +42,10 @@ inline uint8_t DAA(uint8_t a, Flags& flags) if (flags.C || (a > 0x99)) result += 0x60; } - flags.byte = // S Z U5 H U3 P N C - (result & 0x80) | - ((result == 0) ? 0x40 : 0) | - // U5 - ((a ^ result) & 0x10) | - // U3 - flags.P | - (flags.N ? 0x2 : 0) | - ((flags.C || (a > 0x99)) ? 1 : 0); - - // TODO U3 U5 + flags.C = (flags.C || (a > 0x99)) ? 1 : 0; + flags.H = 0; + // flags.N = no change + flags.Z = (result == 0) ? 1 : 0; return result; } diff --git a/DWORD.hh b/DWORD.hh index abf665a..8376fcc 100644 --- a/DWORD.hh +++ b/DWORD.hh @@ -78,6 +78,11 @@ inline std::ostream& operator<<(std::ostream& out, const glBoy::DWORD& val) return out; } +inline std::string hex(const DWORD& val) +{ + return hex(val.host()); +} + // Create a dword from z80-ordered mem. inline DWORD CreateDWORD(uint8_t* in) { diff --git a/FPS.cc b/FPS.cc new file mode 100644 index 0000000..2d9a62e --- /dev/null +++ b/FPS.cc @@ -0,0 +1,96 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + + +#include "glBoy.hh" +#include "FPS.hh" +#include "Log.hh" + +#include + +#include +#include + +using namespace glBoy; + +FPS::FPS(const std::string& displayName, const double& target) : + m_displayName(displayName), + m_targetFPS(target), + m_frameCount(0), + m_fps(0) +{ + gettimeofday(&m_start, 0); +} + +void +FPS::frameComplete() +{ + m_frameCount++; + calcFPS(); +} + +double +FPS::getFPS() +{ + calcFPS(); + return m_fps; +} + +void +FPS::calcFPS() +{ + timeval now; + gettimeofday(&now, NULL); + if (now.tv_sec == m_start.tv_sec) + { + return; + } + + //bool display(now.tv_sec - m_start.tv_sec > 3); + + const double usec(1000000.0); + m_fps = static_cast(m_frameCount) / + ( + (now.tv_sec + now.tv_usec / usec) - + (m_start.tv_sec + m_start.tv_usec / usec) + ); + m_start = now; + m_frameCount = 0; + + //if (display) displayStats(); + displayStats(); +} + +void +FPS::displayStats() +{ + if (m_targetFPS > 0) + { + Stat() << + m_displayName << " FPS = " << m_fps << + " (error=" << std::abs((100 - (m_fps * 100 / m_targetFPS))) << + "%) " << + Log::endl; + } + else + { + Stat() << + m_displayName << " FPS = " << m_fps << + Log::endl; + } +} + diff --git a/FPS.hh b/FPS.hh new file mode 100644 index 0000000..8ad7af1 --- /dev/null +++ b/FPS.hh @@ -0,0 +1,51 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_FPS_HH +#define GLBOY_FPS_HH + +#include "glBoy.hh" +#include + +namespace glBoy +{ + +class FPS +{ +public: + FPS(const std::string& displayName, const double& target); + + double getFPS(); + + void frameComplete(); +private: + void calcFPS(); + void displayStats(); + + std::string m_displayName; + double m_targetFPS; + + timeval m_start; + int m_frameCount; + double m_fps; + +}; + +} // namespace glBoy + +#endif + diff --git a/GameboyCart.cc b/GameboyCart.cc index a48313f..d7d9e37 100644 --- a/GameboyCart.cc +++ b/GameboyCart.cc @@ -32,6 +32,12 @@ #include +#include +#include +#include +#include +#include + using namespace glBoy; using namespace zipper; using namespace std; @@ -69,7 +75,7 @@ namespace { if (offset + bytes <= GameboyCart::MaxRomBytes) { - data.resize(std::max(offset + bytes, data.size())); + data.resize(std::max(offset + bytes, zsize_t(data.size()))); std::copy(inData, inData + bytes, &data[offset]); } else @@ -148,7 +154,7 @@ GameboyCart::init(const std::vector& rom) { m_mbc = "ROM"; - size_t romSize(std::min(0x8000ul, rom.size())); + size_t romSize(std::min(size_t(0x8000ul), rom.size())); shared_ptr mem( new ROM(&rom[0], romSize) ); @@ -162,7 +168,7 @@ GameboyCart::init(const std::vector& rom) { m_mbc = "MBC1"; - size_t romSize(std::min(0x200000ul, rom.size())); + size_t romSize(std::min(size_t(0x200000ul), rom.size())); shared_ptr mem( new MBC1(this, &rom[0], &rom[0] + romSize) ); @@ -176,7 +182,7 @@ GameboyCart::init(const std::vector& rom) { m_mbc = "MBC2"; - size_t romSize(std::min(0x40000ul, rom.size())); + size_t romSize(std::min(size_t(0x40000ul), rom.size())); shared_ptr mem( new MBC2(this, &rom[0], &rom[0] + romSize) ); @@ -197,7 +203,7 @@ GameboyCart::init(const std::vector& rom) { m_mbc = "MBC3"; - size_t romSize(std::min(0x200000ul, rom.size())); + size_t romSize(std::min(size_t(0x200000ul), rom.size())); shared_ptr mem( new MBC3(this, &rom[0], &rom[0] + romSize) ); @@ -325,6 +331,8 @@ GameboyCart::init(const std::vector& rom) tmp << (cartRam / 1024) << "KB"; tmp >> m_extRamSize; } + + loadExtRam(); } void @@ -357,3 +365,79 @@ GameboyCart::isValid() const { return m_isValid; } + +void +GameboyCart::loadExtRam() +{ + if (!m_battery) return; + + struct passwd* entry(getpwuid(getuid())); + if (!entry || !entry->pw_dir) + { + return; + } + + std::stringstream file; + file << entry->pw_dir << "/.glBoy/" << m_title; + int fd(::open(file.str().c_str(), O_RDONLY)); + if (fd < 0) return; + + std::vector >::iterator + it(m_ramBanks.begin()); + for (; it != m_ramBanks.end(); ++it) + { + uint8_t buffer[8192]; + ::read(fd, buffer, sizeof(buffer)); // TODO EINTR ? + for (uint16_t i = 0; i < sizeof(buffer); ++i) + { + (*it)->write8(i, buffer[i]); + } + } + + ::close(fd); +} + + +void +GameboyCart::saveExtRam() +{ + if (!m_battery) return; + + struct passwd* entry(getpwuid(getuid())); + if (!entry || !entry->pw_dir) + { + return; + } + + std::stringstream file; + file << entry->pw_dir << "/.glBoy/"; + + ::mkdir(file.str().c_str(), 0775); + + file << m_title; + + std::stringstream tmpFile; + tmpFile << file.str() << ".tmp"; + int fd(::open(file.str().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0660)); + if (fd < 0) return; + + bool error(false); + + std::vector >::iterator + it(m_ramBanks.begin()); + for (; it != m_ramBanks.end() && !error; ++it) + { + uint8_t buffer[8192]; + std::fill(buffer, buffer + sizeof(buffer), 0); + (*it)->copy(buffer, 0, 0x2000); + int writeResult(::write(fd, buffer, sizeof(buffer))); // TODO EINTR ? + error = writeResult != sizeof(buffer); + } + ::close(fd); + if (!error) + { + // atomicly switch the files. + ::rename(tmpFile.str().c_str(), file.str().c_str()); + } + +} diff --git a/GameboyCart.hh b/GameboyCart.hh index c5c1589..cce1745 100644 --- a/GameboyCart.hh +++ b/GameboyCart.hh @@ -20,7 +20,9 @@ #include "glBoy.hh" #include "Core.hh" +#include #include +#include #include namespace glBoy @@ -29,6 +31,17 @@ namespace glBoy class GameboyCart { public: + class Invalid : public std::runtime_error + { + public: + Invalid(const std::string& what) : std::runtime_error(what) {} + }; + + class Unsupported : public std::runtime_error + { + Unsupported(const std::string& what) : std::runtime_error(what) {} + }; + enum { MinRomBytes = 16384, @@ -43,6 +56,10 @@ public: void dump(std::ostream& out); void setRamBank(size_t bank); + + void loadExtRam(); + void saveExtRam(); + private: void init(const std::vector& rom); Core& m_core; diff --git a/GameboyGraphics.cc b/GameboyGraphics.cc index 651666f..44c1786 100644 --- a/GameboyGraphics.cc +++ b/GameboyGraphics.cc @@ -15,23 +15,15 @@ // You should have received a copy of the GNU General Public License // along with glBoy. If not, see . -#define GL_GLEXT_PROTOTYPES - #include "glBoy.hh" #include "GameboyGraphics.hh" #include "Log.hh" #include "MemoryMap.hh" -#include -#include - -#include - #include #include -#include - +#include using namespace glBoy; @@ -239,15 +231,9 @@ public: GameboyGraphics::GameboyGraphics( - Core& core, - SDL_Surface* surface, - PixelScaler scaler, - ResampleMethod resampler + Core& core ) : m_core(core), - m_scaler(scaler), - m_resampler(resampler), - m_hq4x(160, 144), m_vram(new uint8_t[0x2000]), m_oam(new uint8_t[0x100]), // Should be 0x9F, but many access to 100 m_reg( @@ -258,13 +244,11 @@ GameboyGraphics::GameboyGraphics( ) ), m_backgroundRedraw(true), - m_surface(surface), m_frameSkip(false), - m_lastSkipped(false), - m_stat_frames(0), - m_stat_skipped(0), - m_stat_redraw(0) + m_fps("Renderer", 59.73) { + m_screenShadowMutex = PTHREAD_MUTEX_INITIALIZER; + MemoryMap& mapping(core.getMemoryMap()); mapping.map( 0x8000, @@ -298,12 +282,11 @@ GameboyGraphics::GameboyGraphics( m_mode3 = core.registerPeriodic( 456, 80, std::bind(&glBoy::GameboyGraphics::periodicCallback, this, _1) ); - - gettimeofday(&m_stat_fpsTimer, NULL); } GameboyGraphics::~GameboyGraphics() { + pthread_mutex_destroy(&m_screenShadowMutex); delete[] m_vram; delete[] m_oam; } @@ -359,18 +342,11 @@ GameboyGraphics::startScanLine() { // Draw the entire screen when entering vblank. This will be done just // under 60 times per second. - if (!m_frameSkip || m_lastSkipped) + if (!m_frameSkip) { renderScreen(); - m_lastSkipped = false; - } - else - { - ++m_stat_skipped; - m_lastSkipped = true; } m_frameSkip = false; - displayStats(); m_core.raiseInterrupt(0x40); // Start of vblank if (m_reg->STAT & 0x10) @@ -539,7 +515,6 @@ GameboyGraphics::renderScreen() if (m_backgroundRedraw) { m_backgroundRedraw = false; - ++m_stat_redraw; // Select one of two maps at 0x9800/0x9C00 uint16_t tileMapAddr(m_reg->LCDC & 0x08 ? 0x1C00 : 0x1800); @@ -634,9 +609,12 @@ GameboyGraphics::renderScreen() ); } } - updateOutputDevice(); + pthread_mutex_lock(&m_screenShadowMutex); + ::memcpy(m_screenShadow, m_screen, sizeof(m_screenShadow)); + pthread_mutex_unlock(&m_screenShadowMutex); + m_fps.frameComplete(); } GameboyGraphics::Sprite @@ -653,108 +631,31 @@ GameboyGraphics::getSprite(int spriteNum) const } void -GameboyGraphics::displayStats() -{ - m_stat_frames++; - if (m_stat_frames == 300) - { - timeval startTime(m_stat_fpsTimer); - gettimeofday(&m_stat_fpsTimer, 0); - - const double usec(1000000.0); - double fps = static_cast(m_stat_frames - m_stat_skipped) / - ( - (m_stat_fpsTimer.tv_sec + m_stat_fpsTimer.tv_usec / usec) - - (startTime.tv_sec + startTime.tv_usec / usec) - ); - - Stat() << - "FPS = " << fps << - " (error=" << (100 - (fps * 100 / 59.73)) << "%) " << - "backround redraw=" << m_stat_redraw << - Log::endl; - m_stat_frames = 0; - m_stat_skipped = 0; - m_stat_redraw = 0; - } -} - -void -GameboyGraphics::blitGreyscale(uint8_t* dest) +GameboyGraphics::blitGreyscale(uint8_t* dest, const Pixel* screen) { for (int y = 0; y < 144; ++y) { uint8_t* row(dest + y*160); for (int x = 0; x < 160; ++x) { - int index(m_screen[y][x].colourIndex); - int colour((m_screen[y][x].palette >> (index * 2)) & 0x3); + int index(screen[y*160+x].colourIndex); + int colour((screen[y*160+x].palette >> (index * 2)) & 0x3); row[x] = 255 - ( colour * 85); } } } +// Thread-safe method. +// Call this from the HMI thread, and then display it as +// 160x144 8bpp greyscale. void -GameboyGraphics:: updateOutputDevice() +GameboyGraphics::getGreyscaleFrame(uint8_t* frameBuffer) { - if (SDL_MUSTLOCK(m_surface)) - { - if (SDL_LockSurface(m_surface) < 0) - { - return; - } - } - - // Use static buffers to reduce stack usage - uint8_t textureData[144*160]; - blitGreyscale(&textureData[0]); - - // Allocate texture - GLuint texture; - glGenTextures(1, &texture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - - // Set the texture's stretching properties - // When using opengl shader scalers, we need to use nearest. - uint32_t resample( - (m_scaler == Scaler_None && m_resampler == Resample_Bilinear) ? - GL_LINEAR : GL_NEAREST - ); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, resample); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, resample); - - // Create the texture image - glTexImage2D( - GL_TEXTURE_2D, - 0, - GL_RGBA, - 160, - 144, - 0, - GL_LUMINANCE, - GL_UNSIGNED_BYTE, - &textureData - ); + Pixel screen[144][160]; + pthread_mutex_lock(&m_screenShadowMutex); + ::memcpy(screen, m_screenShadow, sizeof(m_screenShadow)); + pthread_mutex_unlock(&m_screenShadowMutex); - m_hq4x.prepareShader(texture); - - // Output texture. - - glBegin(GL_QUADS); - glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 ); - glTexCoord2i( 1, 0 ); glVertex2i( 160, 0 ); - glTexCoord2i( 1, 1 ); glVertex2i( 160, 144 ); - glTexCoord2i( 0, 1 ); glVertex2i( 0, 144); - glEnd(); - - glLoadIdentity(); - SDL_GL_SwapBuffers(); - glDeleteTextures( 1, &texture ); - - if (SDL_MUSTLOCK(m_surface)) - { - SDL_UnlockSurface(m_surface); - } + blitGreyscale(frameBuffer, &screen[0][0]); } diff --git a/GameboyGraphics.hh b/GameboyGraphics.hh index 1795a6b..295c69c 100644 --- a/GameboyGraphics.hh +++ b/GameboyGraphics.hh @@ -20,33 +20,26 @@ #include "glBoy.hh" #include "Core.hh" - +#include "FPS.hh" #include "MemoryMap.hh" -#include "hq4x.hh" - -#include -#include - #include +#include + namespace glBoy { class GameboyGraphics { public: - enum PixelScaler { Scaler_None, Scaler_Scale2x, Scaler_hq4x }; - enum ResampleMethod { Resample_Nearest, Resample_Bilinear }; - GameboyGraphics( - Core& core, - SDL_Surface* surface, - PixelScaler scaler, - ResampleMethod resampler + Core& core ); ~GameboyGraphics(); + void getGreyscaleFrame(uint8_t* frameBuffer); + void startScanLine(); void renderScanLine(); @@ -79,7 +72,6 @@ private: enum RenderMode{ RENDER_BLIT, RENDER_SPRITE }; void renderScreen(); - void displayStats(); void renderTile( uint8_t tile, @@ -110,16 +102,10 @@ private: }; Sprite getSprite(int spriteNum) const; - void blitGreyscale(uint8_t* dest); - void updateOutputDevice(); + void blitGreyscale(uint8_t* dest, const Pixel* screen); Core& m_core; - PixelScaler m_scaler; - ResampleMethod m_resampler; - - Hq4x m_hq4x; - // Graphics system operates with 8x8 pixel tiles. There is space for 384 // tiles. 256 tiles map be used in a map. Map 1 uses tile numbers 0 to 255, // other uses -128 to 127. @@ -143,11 +129,9 @@ private: Pixel m_background[256][256]; Pixel m_screen[144][160]; - // Output device - SDL_Surface* m_surface; - - // Shader program - GLint m_shaderProgram; + // Copy of raster display for sending to the hardware-dependant code. + pthread_mutex_t m_screenShadowMutex; + Pixel m_screenShadow[144][160]; Core::CallbackId m_mode0; Core::CallbackId m_mode2; @@ -156,14 +140,9 @@ private: int m_lineScrollX[154]; int m_lineScrollY[154]; - bool m_frameSkip; - bool m_lastSkipped; - timeval m_stat_fpsTimer; - int m_stat_frames; - int m_stat_skipped; - int m_stat_redraw; + FPS m_fps; }; } // namespace glBoy diff --git a/GameboyJoypad.cc b/GameboyJoypad.cc index dd5244a..f1f4b3e 100644 --- a/GameboyJoypad.cc +++ b/GameboyJoypad.cc @@ -27,77 +27,24 @@ using namespace glBoy; // TODO implement interrupt! -namespace +GameboyJoypad::GameboyJoypad(Core& core) : + m_core(core), + m_state(Joypad_Off), + m_buttonState(0xffff) { - void setConfiguredKey( - const std::map& keyMap, - const libconfig::Config& config, - const std::string& configParam, - int& mappedKey - ) - { - if (config.exists(configParam)) - { - std::string value; - config.lookupValue(configParam, value); - std::map::const_iterator it( - keyMap.find(value) - ); - if (!value.empty() && it != keyMap.end()) - { - mappedKey = it->second; - } - } - } } -GameboyJoypad::GameboyJoypad(Core& core, const libconfig::Config& config) : - m_state(Joypad_Off) +uint8_t +GameboyJoypad::read8(uint16_t) { - std::map sdlKeyMap; - - for (int i = SDLK_FIRST; i < SDLK_LAST; ++i) + // Thread-safe non-blocking read. + SharedState buttonState(m_buttonState); + while ( + !__sync_bool_compare_and_swap(&m_buttonState, buttonState, buttonState)) { - std::string keyName = SDL_GetKeyName(static_cast(i)); - if (keyName.find("SDLK_") == 0) - { - keyName = keyName.substr(5); - } - sdlKeyMap[keyName] = i; + buttonState = m_buttonState; } - m_inputs[DOWN].sdlKey = SDLK_DOWN; - //m_inputs[DOWN].sdlJoystick = 0 or none or something; - m_inputs[UP].sdlKey = SDLK_UP; - m_inputs[LEFT].sdlKey = SDLK_LEFT; - m_inputs[RIGHT].sdlKey = SDLK_RIGHT; - m_inputs[START].sdlKey = SDLK_RETURN; - m_inputs[SELECT].sdlKey = SDLK_BACKSPACE; - m_inputs[A].sdlKey = SDLK_a; - m_inputs[B].sdlKey = SDLK_s; - - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.up", m_inputs[UP].sdlKey); - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.down", m_inputs[DOWN].sdlKey); - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.left", m_inputs[LEFT].sdlKey); - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.right", m_inputs[RIGHT].sdlKey); - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.start", m_inputs[START].sdlKey); - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.select", m_inputs[SELECT].sdlKey); - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.a", m_inputs[A].sdlKey); - setConfiguredKey(sdlKeyMap, config, "glBoy.keys.b", m_inputs[B].sdlKey); - - pollInput(0); - - using namespace std::placeholders; - core.registerPeriodic( - 32768, // 128Hz - 0, - std::bind(&glBoy::GameboyJoypad::pollInput, this, _1) - ); -} - -uint8_t -GameboyJoypad::read8(uint16_t) -{ uint8_t result(0xff); switch (m_state) @@ -107,11 +54,11 @@ GameboyJoypad::read8(uint16_t) break; case Joypad_Directional: - result = m_directionalState; + result = buttonState >> 8; break; case Joypad_Button: - result = m_buttonState; + result = buttonState & 0xff; break; }; return result; @@ -142,51 +89,34 @@ GameboyJoypad::write8(uint16_t, uint8_t value) } void -GameboyJoypad::pollInput(Core::CallbackId) +GameboyJoypad::setInput( + bool up, + bool down, + bool left, + bool right, + bool start, + bool select, + bool a, + bool b) { - SDL_Event event; - - while (SDL_PollEvent(&event)) - { - switch(event.type) - { - case SDL_KEYUP: - case SDL_KEYDOWN: - { - if (event.key.keysym.sym == SDLK_ESCAPE) - { - exit(0); - } - - for (int i = 0; i < 8; ++i) - { - if (event.key.keysym.sym == m_inputs[i].sdlKey) - { - m_inputs[i].depressed = (event.key.type == SDL_KEYDOWN); - break; - } - } - }; break; - - case SDL_QUIT: - exit(0); - break; - - default: - break; - } - } - - m_directionalState = 0xff; - if (m_inputs[DOWN].depressed) m_directionalState ^= 0x8; - if (m_inputs[UP].depressed) m_directionalState ^= 0x4; - if (m_inputs[LEFT].depressed) m_directionalState ^= 0x2; - if (m_inputs[RIGHT].depressed) m_directionalState ^= 0x1; - - m_buttonState = 0xff; - if (m_inputs[START].depressed) m_buttonState ^= 0x8; - if (m_inputs[SELECT].depressed) m_buttonState ^= 0x4; - if (m_inputs[B].depressed) m_buttonState ^= 0x2; - if (m_inputs[A].depressed) m_buttonState ^= 0x1; + SharedState buttonState(0xffff); + if (down) buttonState ^= (0x8 << 8); + if (up) buttonState ^= (0x4 << 8); + if (left) buttonState ^= (0x2 << 8); + if (right) buttonState ^= (0x1 << 8); + + if (start) buttonState ^= 0x8; + if (select) buttonState ^= 0x4; + if (b) buttonState ^= 0x2; + if (a) buttonState ^= 0x1; + + // Thread-safe. Assume the read8 and write8 methods will be called + // in a different thread to that handling user input. We need to + SharedState old(m_buttonState); + bool cmpResult( + __sync_bool_compare_and_swap(&m_buttonState, old, buttonState)); + + // There should only be one thread writing to m_buttonState + assert(cmpResult); } diff --git a/GameboyJoypad.hh b/GameboyJoypad.hh index 8662875..ab9fbb7 100644 --- a/GameboyJoypad.hh +++ b/GameboyJoypad.hh @@ -23,39 +23,37 @@ #include "MemoryMap.hh" -#include -#include - namespace glBoy { class GameboyJoypad : public MemoryMap::Memory { public: - GameboyJoypad(Core& core, const libconfig::Config& config); - - void pollInput(Core::CallbackId id); + GameboyJoypad(Core& core); + + // Thread safe + void setInput( + bool up, + bool down, + bool left, + bool right, + bool start, + bool select, + bool a, + bool b); virtual uint8_t read8(uint16_t address); virtual void write8(uint16_t address, uint8_t value); private: + Core& m_core; + enum State { Joypad_Off, Joypad_Directional, Joypad_Button }; State m_state; - struct Input - { - Input() : sdlKey(0), depressed(false) {} - int sdlKey; - int sdlJoystick; - bool depressed; - }; - - enum Inputs { UP = 0, DOWN, LEFT, RIGHT, A, B, START, SELECT }; - Input m_inputs[8]; - - uint8_t m_buttonState; - uint8_t m_directionalState; + // Data is (possible) used in both user input and emulation threads. + typedef unsigned int SharedState; + SharedState m_buttonState; }; } // namespace glBoy diff --git a/GameboySound.cc b/GameboySound.cc index 09c0734..694b42b 100644 --- a/GameboySound.cc +++ b/GameboySound.cc @@ -24,34 +24,8 @@ using namespace glBoy; -enum { SAMPLE_RATE = 48000 }; - -extern "C" -{ - void mixaudio(void *userData, Uint8 *stream, int len) - { - GameboySound* ourObject(reinterpret_cast(userData)); - ourObject->mixSounds(stream, len); - } -} - GameboySound::GameboySound(Core& core) { - SDL_AudioSpec fmt; - - fmt.freq = SAMPLE_RATE; - fmt.format = AUDIO_S16SYS; - fmt.channels = 1; - fmt.samples = 512; - fmt.callback = mixaudio; - fmt.userdata = this; - - if ( SDL_OpenAudio(&fmt, NULL) < 0 ) - { - assert(false); - } - SDL_PauseAudio(0); - using namespace std::placeholders; core.registerPeriodic( 32768, // 128 Hz @@ -235,41 +209,48 @@ void GameboySound::periodicUpdate(Core::CallbackId) } void -GameboySound::mixSounds(Uint8 *stream, int len) +GameboySound::mixSounds(int16_t *pcmStream, size_t samples) { - for (int i = 0; i < len / 2; ++i) + enum { MaxChunkSize = 512 }; + int32_t pcmBuffer[MaxChunkSize]; + std::fill(pcmBuffer, pcmBuffer + MaxChunkSize, 0); + while (samples > 0) { - int32_t val(0); - val += sample(m_channel1); - val += sample(m_channel2); - val += sampleChannel3(); - val += sampleChannel4(); - val >>= 2; - if (val > 32767) val = 32767; - if (val < -32768) val = -32768; - -//TODO ENDIAN FIAL HERE - stream[i * 2] = val & 0xff; - stream[i * 2 + 1] = val >> 8; - } + size_t chunk = std::min(samples, size_t(MaxChunkSize)); + + mixSamples(m_channel1, pcmBuffer, chunk); + mixSamples(m_channel2, pcmBuffer, chunk); + mixSamplesChannel3(pcmBuffer, chunk); + mixSamplesChannel4(pcmBuffer, chunk); + for (size_t i = 0; i < chunk; ++i) + { + pcmStream[i] = (pcmBuffer[i] / 4); // Can't overflow. + } + samples -= chunk; + pcmStream += chunk; + } } -int16_t -GameboySound::sample(SquareWave& channel) +void +GameboySound::mixSamples( + SquareWave& channel, int32_t* pcmBuffer, size_t samples) { - int16_t sample(0); + //int32_t sampleVolume(channel.currentVolume * channel.currentVolume); + //sampleVolume = sampleVolume * sampleVolume; + int32_t sampleVolume(channel.currentVolume * 2048); - if (channel.currentVolume && channel.remainingLength > 0) + size_t i; + for (i = 0; (i < samples) && (channel.remainingLength > 0); ++i) { int pos = channel.squareStep / (channel.samplesPerDutyBit); - sample = channel.currentVolume * channel.currentVolume; - sample = sample * sample; + int32_t sample(sampleVolume); if (!((channel.dutyMask >> (7 - pos)) & 1)) { sample = -sample; } + pcmBuffer[i] += sample; ++channel.squareStep; if (channel.squareStep > (channel.samplesPerDutyBit * 8)) @@ -277,7 +258,7 @@ GameboySound::sample(SquareWave& channel) channel.squareStep = 0; } } - return sample; + std::fill(pcmBuffer + i, pcmBuffer + samples, 0); } void @@ -350,27 +331,31 @@ GameboySound::prepChannel(SquareWave& channel) channel.samplesPerDutyBit = (SAMPLE_RATE/freq) / 8; } -int16_t -GameboySound::sampleChannel4() +void +GameboySound::mixSamplesChannel4(int32_t* pcmBuffer, size_t samples) { - int16_t sample(0); + //int32_t sampleVolume(m_channel4.currentVolume * m_channel4.currentVolume); + //sampleVolume = sampleVolume * sampleVolume; + int32_t sampleVolume(m_channel4.currentVolume * 2048); - if (m_channel4.currentVolume && m_channel4.remainingLength > 0) + size_t i; + for (i = 0; (i < samples) && (m_channel4.remainingLength > 0); ++i) { - sample = m_channel4.currentVolume * m_channel4.currentVolume; - sample = sample * sample; + int32_t sample(sampleVolume); if (!m_channel4.high) { sample = -sample; } + pcmBuffer[i] += sample; ++m_channel4.currentCounter; if (m_channel4.currentCounter > m_channel4.samplesPerStep) { m_channel4.high = rand() & 0x1; + m_channel4.currentCounter = 0; } } - return sample; + std::fill(pcmBuffer + i, pcmBuffer + samples, 0); } @@ -437,17 +422,18 @@ GameboySound::prepChannel3() m_channel3.samplesPerNibble = (SAMPLE_RATE/freq) / 32; } -int16_t -GameboySound::sampleChannel3() +void +GameboySound::mixSamplesChannel3(int32_t* pcmBuffer, size_t samples) { - int16_t sample(0); - - if (m_channel3.remainingLength > 0 && - (m_channel3.enabled & 0x80) - ) + int volume((m_channel3.volume >> 5) & 0x3); + + size_t i; + for (i = 0; + (i < samples) && + (m_channel3.remainingLength) > 0 && + (m_channel3.enabled & 0x80); + ++i) { - int volume((m_channel3.volume >> 5) & 0x3); - uint8_t nibble = m_channel3.waveTable[m_channel3.wavePos / 2]; if (m_channel3.wavePos & 0x1) @@ -469,11 +455,11 @@ GameboySound::sampleChannel3() } else if (volume == 3) { - nibble >>= 3; + nibble >>= 2; } // Ok, nibble is now between 0 and 0xf. Map this to full range. - sample = (static_cast(nibble) - 8) * 4096; + pcmBuffer[i] += (static_cast(nibble) - 8) * 4096; ++m_channel3.waveSamples; if (m_channel3.waveSamples > m_channel3.samplesPerNibble * (m_channel3.wavePos + 1)) @@ -487,5 +473,6 @@ GameboySound::sampleChannel3() } } - return sample; + std::fill(pcmBuffer + i, pcmBuffer + samples, 0); } + diff --git a/GameboySound.hh b/GameboySound.hh index 8a01d39..039898b 100644 --- a/GameboySound.hh +++ b/GameboySound.hh @@ -23,14 +23,14 @@ #include "MemoryMap.hh" -#include - namespace glBoy { class GameboySound : public MemoryMap::Memory { public: + enum Constants { SAMPLE_RATE = 48000 }; + GameboySound(Core& core); void periodicUpdate(Core::CallbackId id); @@ -38,10 +38,9 @@ public: virtual uint8_t read8(uint16_t address); virtual void write8(uint16_t address, uint8_t value); - void mixSounds(Uint8* stream, int len); + // TODO: Thread safe + void mixSounds(int16_t* pcmStream, size_t samples); private: - int16_t getSample(uint8_t volume); - struct SquareWave { SquareWave() : sweep(0), duty(0), volume(0), freqLow(0), freqHigh(0) @@ -97,7 +96,7 @@ private: int remainingLength; }; - int16_t sample(SquareWave& channel); + void mixSamples(SquareWave& channel, int32_t* pcmBuffer, size_t samples); void prepChannel(SquareWave& channel); SquareWave m_channel1; @@ -134,7 +133,7 @@ private: }; Noise m_channel4; - int16_t sampleChannel4(); + void mixSamplesChannel4(int32_t* pcmBuffer, size_t samples); void prepChannel4(); struct Wave @@ -178,7 +177,7 @@ private: }; Wave m_channel3; - int16_t sampleChannel3(); + void mixSamplesChannel3(int32_t* pcmBuffer, size_t samples); void prepChannel3(); uint8_t m_channelControl; diff --git a/GameboyTimer.cc b/GameboyTimer.cc index 889eb2a..5bd7b0b 100644 --- a/GameboyTimer.cc +++ b/GameboyTimer.cc @@ -22,10 +22,19 @@ #include #include +#include #include using namespace glBoy; +enum +{ + GB_HZ = 4194304, // DMG freq ~ 4MHz + IF_HZ = 16384, // Intermediate Frequency. Must be a power-of-2 + IF_PERIOD_NS = 61035, // (1.0/IF_HZ * 1e9) + MIN_SLEEP_NS = 500000 +}; + GameboyTimer::GameboyTimer( Core& core, const std::function& setFrameSkip, @@ -33,13 +42,14 @@ GameboyTimer::GameboyTimer( ) : m_core(core), m_setSkipFrame(setFrameSkip), - m_clearSkipFrame(clearFrameSkip) + m_clearSkipFrame(clearFrameSkip), + m_tick(0) { memset(m_reg, 0, 4); using namespace std::placeholders; core.registerPeriodic( - 256, + GB_HZ / IF_HZ, 0, std::bind(&glBoy::GameboyTimer::tick, this, _1) ); @@ -47,31 +57,45 @@ GameboyTimer::GameboyTimer( timespec res; clock_getres(CLOCK_REALTIME, &res); - // Make sure we can perform high-resolution sleeps -// TODO resolve this -// meh. high resolution not all that necessary. - m_clockSleepDelay = std::min(res.tv_nsec / 60000, 10l); - //m_clockSleepDelay = res.tv_nsec / 60000; + long clockSleepNs = std::max(res.tv_nsec, long(MIN_SLEEP_NS)); + m_clockSleepDelay = clockSleepNs / IF_PERIOD_NS; m_clockSleepCounter = m_clockSleepDelay; + m_frameSkipAllowanceNs = std::max(long(1e9/60), 10 * clockSleepNs); -/* - if (m_clockSleepDelay) + if (clockSleepNs > MIN_SLEEP_NS) { - Log::Instance()(Log::DBG_WARNING) << - "Timer delay set to " << m_clockSleepDelay << - " nanoseconds. Enable HR timers for improved emulation" << std::endl; + Warn() << + "Timer delay set to " << clockSleepNs << + " nanoseconds. Enable HR timers for improved emulation" << + Log::endl; } -*/ clock_gettime(CLOCK_REALTIME, &m_lastSlept); } -// Call this every 256 clock cycles. (16384Hz) +template +static inline void +increment(unsigned int freq, unsigned int tick, T& counter) +{ + // freq and IF_HZ MUST be a power-of-two. + if (freq < IF_HZ) + { + if (tick % (IF_HZ / freq) == 0) ++counter; + } + else + { + counter += freq / IF_HZ; + } +} void GameboyTimer::tick(Core::CallbackId) { - ++m_reg[DIV]; + // Note: This will overrun! code should not depend on differences between + // any successive values. + ++m_tick; + + increment(16384, m_tick, m_reg[DIV]); if (m_reg[TAC] & 0x4) // Timer enabled { @@ -79,16 +103,16 @@ GameboyTimer::tick(Core::CallbackId) switch (m_reg[TAC] & 0x3) { case 0: // 4096Hz - if (m_reg[DIV] % 4 == 0) ++counter; + increment(4096, m_tick, counter); break; - case 1: // 268400Hz - counter += 16; + case 1: // 262144 + increment(262144, m_tick, counter); break; case 2: // 65536Hz - counter += 4; + increment(65536, m_tick, counter); break; case 3: // 16384Hz - ++counter; + increment(16384, m_tick, counter); break; } @@ -103,39 +127,44 @@ GameboyTimer::tick(Core::CallbackId) } } - m_lastSlept.tv_nsec += 61035; + m_lastSlept.tv_nsec += (1.0/IF_HZ) * 1e9; if (m_lastSlept.tv_nsec > 1e9) { m_lastSlept.tv_nsec -= 1e9; m_lastSlept.tv_sec++; } + --m_clockSleepCounter; if (m_clockSleepCounter == 0) { timespec now; clock_gettime(CLOCK_REALTIME, &now); - if ( - (m_lastSlept.tv_sec > now.tv_sec) || - ( - (m_lastSlept.tv_sec == now.tv_sec) && - (m_lastSlept.tv_nsec > now.tv_nsec) - ) - ) + + int64_t nowNs(now.tv_sec * uint64_t(1e9) + now.tv_nsec); + int64_t lastNs(m_lastSlept.tv_sec * uint64_t(1e9) + m_lastSlept.tv_nsec); + + if (lastNs > nowNs) { m_clearSkipFrame(); clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &m_lastSlept, NULL); } - else + else if (nowNs - lastNs > (1e9 / 4)) + { + // We're significantly behind. At this point, the user will + // experience a glitch no matter what we do. + // "Pretend" we've caught up, and hopefully it will be good from + // this point forward. + m_clearSkipFrame(); + m_lastSlept = now; + } + else if (nowNs - lastNs > m_frameSkipAllowanceNs) { + // If we start dragging the chain by a user-visible amount, + // then start frame skipping. m_setSkipFrame(); } m_clockSleepCounter = m_clockSleepDelay; } - else - { - m_clockSleepCounter--; - } - } uint8_t diff --git a/GameboyTimer.hh b/GameboyTimer.hh index 2d5df43..4bcf079 100644 --- a/GameboyTimer.hh +++ b/GameboyTimer.hh @@ -25,8 +25,6 @@ #include -#include - namespace glBoy { @@ -57,6 +55,9 @@ private: int m_clockSleepDelay; int m_clockSleepCounter; + long m_frameSkipAllowanceNs; + + unsigned int m_tick; }; } // namespace glBoy diff --git a/InstructionSet.opcode b/InstructionSet.opcode index ca022d8..bb40785 100644 --- a/InstructionSet.opcode +++ b/InstructionSet.opcode @@ -47,7 +47,6 @@ - @@ -258,6 +257,19 @@ m_mem.write16(m_reg.SP, q); + + + m_reg.SP.dec(2); + m_reg.F.byte &= 0xF0; + m_mem.write16(m_reg.SP, m_reg.AF); + + + + + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.AF); + + m_reg.SP.dec(2); @@ -276,6 +288,19 @@ m_reg.SP.inc(2); + + + m_reg.AF = m_mem.read16(m_reg.SP); + m_reg.F.byte &= 0xF0; + m_reg.SP.inc(2); + + + + + m_reg.AF = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + + m_reg.IX = m_mem.read16(m_reg.SP); @@ -645,12 +670,16 @@ + bit oldC(m_reg.F.C); r = ALU::add(r, 1, m_reg.F); + m_reg.F.C = oldC; + bit oldC(m_reg.F.C); m_mem.write8(m_reg.HL, ALU::add(m_mem.read8(m_reg.HL), 1, m_reg.F)); + m_reg.F.C = oldC; @@ -665,12 +694,16 @@ + bit oldC(m_reg.F.C); r = ALU::sub(r, 1, m_reg.F); + m_reg.F.C = oldC; + bit oldC(m_reg.F.C); m_mem.write8(m_reg.HL, ALU::sub(m_mem.read8(m_reg.HL), 1, m_reg.F)); + m_reg.F.C = oldC; @@ -708,6 +741,7 @@ m_reg.F.C = (m_reg.F.C) ? 0 : 1; + m_reg.F.H = 0; m_reg.F.N = 0; @@ -730,14 +764,12 @@ - m_iff1 = 0; - m_iff2 = 0; + m_ime = 0; - m_iff1 = 1; - m_iff2 = 1; + m_ime = 1; @@ -1326,8 +1358,7 @@ m_reg.PC = m_mem.read16(m_reg.SP); m_reg.SP.inc(2); - m_iff1 = 1; - m_iff2 = 1; + m_ime = 1; @@ -1340,7 +1371,7 @@ // TODO interruptComplete(); - + uint8_t addr; switch (b) @@ -1450,8 +1481,7 @@ TODO NOT IMPLEMENTED m_reg.PC = m_mem.read16(m_reg.SP); m_reg.SP.inc(2); - m_iff1 = 1; - m_iff2 = 1; + m_ime = 1; @@ -1462,7 +1492,7 @@ TODO NOT IMPLEMENTED - m_reg.SP = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F); + m_reg.SP = ALU::add(m_reg.SP, d, m_reg.F); @@ -1471,9 +1501,9 @@ TODO NOT IMPLEMENTED - + - m_reg.HL = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F); + m_reg.HL = ALU::add(m_reg.SP, d, m_reg.F); @@ -1626,19 +1656,24 @@ TODO NOT IMPLEMENTED m_reg.F.C = 0; + - + m_mem.write8(htoz(0xFF00 + n), m_reg.A); - + m_mem.write8(htoz(0xFF00 + m_reg.C), m_reg.A); + + + m_reg.A = m_mem.read8(htoz(0xFF00 + m_reg.C)); + - + m_reg.A = m_mem.read8(htoz(0xFF00 + n)); @@ -1673,9 +1708,6 @@ TODO NOT IMPLEMENTED - - - diff --git a/Log.cc b/Log.cc index 62dc21d..55c6916 100644 --- a/Log.cc +++ b/Log.cc @@ -60,6 +60,10 @@ Log& glBoy::TraceMem() return GetLog(Log::DBG_TRACE_MEM); } +Log& glBoy::TraceCPU() +{ + return GetLog(Log::DBG_TRACE_CPU); +} Log& Log::Instance() @@ -70,7 +74,8 @@ Log::Instance() Log::Log() : m_levelMask(15), - m_currentLevel(DBG_NONE) + m_currentLevel(DBG_NONE), + m_nullStream(&m_nullBuf) { char* env(getenv("GLBOY_DEBUG_LEVEL")); if (env) diff --git a/Log.hh b/Log.hh index 8aaf419..68fa1cc 100644 --- a/Log.hh +++ b/Log.hh @@ -34,6 +34,7 @@ namespace glBoy Log& Info(); Log& Stat(); Log& TraceMem(); + Log& TraceCPU(); class Log { @@ -52,6 +53,7 @@ namespace glBoy DBG_INFO = 4, DBG_STAT = 8, DBG_TRACE_MEM = 16, + DBG_TRACE_CPU = 32, DBG_NONE = 0xFFFFFFFF }; @@ -95,6 +97,7 @@ namespace glBoy void setDebugLevel(uint32_t levelMask); bool isDebugLevelOn(DebugLevel level) const; + std::ostream& nullStream() { return m_nullStream; } private: friend Log& GetLog(int); @@ -108,6 +111,14 @@ namespace glBoy DebugLevel m_currentLevel; std::stringstream m_collector; + + class NullBuf : public std::streambuf + { + public: + }; + + NullBuf m_nullBuf; + std::ostream m_nullStream; }; } // namespace glBoy diff --git a/MBC1.cc b/MBC1.cc index 31defe3..ded039c 100644 --- a/MBC1.cc +++ b/MBC1.cc @@ -17,9 +17,10 @@ #include "glBoy.hh" #include "MBC1.hh" +#include "GameboyCart.hh" #include "Log.hh" -#include +#include using namespace glBoy; using namespace std; @@ -29,15 +30,51 @@ MBC1::MBC1( ) : m_parent(parent), m_rom(begin, end), + m_bankSize((end - begin) / 0x4000), m_mode(ROM_SELECT), m_romLow(1), m_romHigh(0), - m_romBank(0) + m_romBank(1), + m_ramEnabled(false) { if (end - begin < 0x8000) { - Err() << "MBC1 ROM too small (" << end - begin << " bytes)" << Log::endl; - abort(); + std::stringstream error; + error << "MBC1 ROM too small (" << end - begin << " bytes)"; + throw GameboyCart::Invalid(error.str()); + } + else if ((end - begin) % 0x4000) + { + std::stringstream error; + error << "MBC1 ROM invalid size (" << end - begin << " bytes)"; + throw GameboyCart::Invalid(error.str()); + + } +} + +uint16_t +MBC1::getBankIndex(uint16_t address) const +{ + if (address < 0x4000) + { + return 0; + } + else + { + return m_romBank; + } +} + +uint16_t +MBC1::getBankSize(uint16_t address) const +{ + if (address < 0x4000) + { + return 0; + } + else + { + return m_bankSize; } } @@ -55,7 +92,7 @@ MBC1::read8(uint16_t address) else { TraceMem() << "Attempt to read unmapped MBC1 rom data." << Log::endl; - abort(); + return 0; } } @@ -64,7 +101,18 @@ MBC1::write8(uint16_t address, uint8_t value) { if (address < 0x1FFF) { - // TODO enable/disable ram + if ((value & 0xf) == 0xa) // enable + { + m_ramEnabled = true; + } + else + { + if (m_ramEnabled) + { + m_parent->saveExtRam(); + } + m_ramEnabled = false; + } } else if (address < 0x4000) { @@ -76,7 +124,6 @@ MBC1::write8(uint16_t address, uint8_t value) m_romBank |= (m_romHigh << 5); } -std::cerr << "MBC1 ROM LOW " << (int)m_romBank << std::endl; } else if (address < 0x6000) { @@ -88,7 +135,6 @@ std::cerr << "MBC1 ROM LOW " << (int)m_romBank << std::endl; else { m_romBank = m_romLow | (m_romHigh << 5); -std::cerr << "MBC1 ROM HIGH " << (int)m_romBank << std::endl; } } else @@ -100,14 +146,12 @@ std::cerr << "MBC1 ROM HIGH " << (int)m_romBank << std::endl; m_parent->setRamBank(m_romHigh); m_romBank = m_romLow; -std::cerr << "MBC1 RAM SELECT " << (int)m_romBank << std::endl; } else { m_mode = ROM_SELECT; m_parent->setRamBank(0); m_romBank = m_romLow | (m_romHigh << 5); -std::cerr << "MBC1 ROM SELECT " << (int)m_romBank << std::endl; } } } diff --git a/MBC1.hh b/MBC1.hh index a2b61af..67394ba 100644 --- a/MBC1.hh +++ b/MBC1.hh @@ -33,6 +33,9 @@ namespace glBoy const uint8_t* begin, const uint8_t* end); + virtual uint16_t getBankIndex(uint16_t address) const; + virtual uint16_t getBankSize(uint16_t address) const; + virtual uint8_t read8(uint16_t address); virtual void write8(uint16_t address, uint8_t value); @@ -40,6 +43,7 @@ namespace glBoy GameboyCart* m_parent; std::vector m_rom; + size_t m_bankSize; enum Mode { ROM_SELECT, RAM_SELECT }; Mode m_mode; @@ -48,6 +52,8 @@ namespace glBoy uint8_t m_romHigh; uint32_t m_romBank; + + bool m_ramEnabled; }; } diff --git a/MBC2.cc b/MBC2.cc index 000a830..156ed84 100644 --- a/MBC2.cc +++ b/MBC2.cc @@ -20,6 +20,7 @@ #include "Log.hh" #include +#include using namespace glBoy; using namespace std; @@ -29,12 +30,48 @@ MBC2::MBC2( ) : m_parent(parent), m_rom(begin, end), - m_romBank(0) + m_bankSize((end - begin) / 0x4000), + m_romBank(1), + m_ramEnabled(false) { if (end - begin < 0x8000) { - Err() << "MBC2 ROM too small (" << end - begin << " bytes)" << Log::endl; - abort(); + std::stringstream error; + error << "MBC2 ROM too small (" << end - begin << " bytes)"; + throw GameboyCart::Invalid(error.str()); + } + else if ((end - begin) % 0x4000) + { + std::stringstream error; + error << "MBC2 ROM invalid size (" << end - begin << " bytes)"; + throw GameboyCart::Invalid(error.str()); + + } +} + +uint16_t +MBC2::getBankIndex(uint16_t address) const +{ + if (address < 0x4000) + { + return 0; + } + else + { + return m_romBank; + } +} + +uint16_t +MBC2::getBankSize(uint16_t address) const +{ + if (address < 0x4000) + { + return 0; + } + else + { + return m_bankSize; } } @@ -52,7 +89,7 @@ MBC2::read8(uint16_t address) else { TraceMem() << "Attempt to read unmapped MBC2 rom data." << Log::endl; - abort(); + return 0; } } @@ -61,7 +98,21 @@ MBC2::write8(uint16_t address, uint8_t value) { if (address < 0x1FFF) { - // TODO enable/disable ram + if ((address & 0x10) == 0) + { + if ((value & 0xf) == 0xa) // enable + { + m_ramEnabled = true; + } + else + { + if (m_ramEnabled) + { + m_parent->saveExtRam(); + } + m_ramEnabled = false; + } + } } else if (address < 0x4000) { diff --git a/MBC2.hh b/MBC2.hh index 1445f83..2fc178e 100644 --- a/MBC2.hh +++ b/MBC2.hh @@ -33,6 +33,8 @@ namespace glBoy const uint8_t* begin, const uint8_t* end); + virtual uint16_t getBankIndex(uint16_t address) const; + virtual uint16_t getBankSize(uint16_t address) const; virtual uint8_t read8(uint16_t address); virtual void write8(uint16_t address, uint8_t value); @@ -40,8 +42,11 @@ namespace glBoy GameboyCart* m_parent; std::vector m_rom; + size_t m_bankSize; uint32_t m_romBank; + + bool m_ramEnabled; }; } diff --git a/MBC3.cc b/MBC3.cc index 750a38b..91d14a9 100644 --- a/MBC3.cc +++ b/MBC3.cc @@ -21,6 +21,8 @@ #include +#include + using namespace glBoy; using namespace std; @@ -29,15 +31,52 @@ MBC3::MBC3( ) : m_parent(parent), m_rom(begin, end), - m_romBank(0) + m_bankSize((end - begin) / 0x4000), + m_romBank(1), + m_ramEnabled(false) { if (end - begin < 0x8000) { - Err() << "MBC3 ROM too small (" << end - begin << " bytes)" << Log::endl; - abort(); + std::stringstream error; + error << "MBC3 ROM too small (" << end - begin << " bytes)"; + throw GameboyCart::Invalid(error.str()); + } + else if ((end - begin) % 0x4000) + { + std::stringstream error; + error << "MBC3 ROM invalid size (" << end - begin << " bytes)"; + throw GameboyCart::Invalid(error.str()); + } } +uint16_t +MBC3::getBankIndex(uint16_t address) const +{ + if (address < 0x4000) + { + return 0; + } + else + { + return m_romBank; + } +} + +uint16_t +MBC3::getBankSize(uint16_t address) const +{ + if (address < 0x4000) + { + return 0; + } + else + { + return m_bankSize; + } +} + + uint8_t MBC3::read8(uint16_t address) { @@ -52,7 +91,7 @@ MBC3::read8(uint16_t address) else { TraceMem() << "Attempt to read unmapped MBC3 rom data." << Log::endl; - abort(); + return 0; } } @@ -63,7 +102,18 @@ MBC3::write8(uint16_t address, uint8_t value) if (address < 0x1FFF) { - // TODO enable/disable ram + if ((value & 0xf) == 0xa) // enable + { + m_ramEnabled = true; + } + else + { + if (m_ramEnabled) + { + m_parent->saveExtRam(); + } + m_ramEnabled = false; + } } else if (address < 0x4000) { diff --git a/MBC3.hh b/MBC3.hh index 0b7081a..c824720 100644 --- a/MBC3.hh +++ b/MBC3.hh @@ -33,6 +33,8 @@ namespace glBoy const uint8_t* begin, const uint8_t* end); + virtual uint16_t getBankIndex(uint16_t address) const; + virtual uint16_t getBankSize(uint16_t address) const; virtual uint8_t read8(uint16_t address); virtual void write8(uint16_t address, uint8_t value); @@ -40,8 +42,11 @@ namespace glBoy GameboyCart* m_parent; std::vector m_rom; + size_t m_bankSize; uint32_t m_romBank; + + bool m_ramEnabled; }; } diff --git a/Makefile.am b/Makefile.am index 0dad624..469250e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,13 +17,6 @@ SUBDIRS = libzipper -check_PROGRAMS = \ - test_alu - -TESTS = $(check_PROGRAMS) - -test_alu_SOURCES = Test.cc ALU.cc - dist_noinst_SCRIPTS = autogen.sh EXTRA_DIST = \ @@ -40,11 +33,12 @@ bin_PROGRAMS = glBoy glBoy_SOURCES = \ ALU.hh \ - ALU.cc \ Core.hh \ Core.cc \ DAA.hh \ DWORD.hh \ + FPS.hh \ + FPS.cc \ GameboyCart \ GameboyCart.cc \ GameboyGraphics.hh \ @@ -60,7 +54,6 @@ glBoy_SOURCES = \ hq4x.cc \ Log.hh \ Log.cc \ - Main.cc \ MemoryMap.hh \ MemoryMap.cc \ MBC1.hh \ @@ -77,7 +70,14 @@ glBoy_SOURCES = \ Registers.cc \ ROM.hh \ ROM.cc \ - util.hh + util.hh \ + sdl/GLGraphics.hh \ + sdl/GLGraphics.cc \ + sdl/Main.cc \ + sdl/SDLKeys.hh \ + sdl/SDLKeys.cc \ + sdl/SDLSound.hh \ + sdl/SDLSound.cc BUILT_SOURCES = \ InstructionSet_Opcodes.cc @@ -93,12 +93,10 @@ glBoy_LDADD = libzipper/libzipper.la glBoy_LDFLAGS = -lGL -lGLU -CXXFLAGS=-g -O3 -march=native -W -Wall -Werror -std=c++0x +CXXFLAGS+=-g -O3 -march=native -W -Wall -Werror -std=c++0x +#CXXFLAGS=-g -march=native -W -Wall -Werror -std=c++0x +#CPPFLAGS+=-DGLBOY_DEBUG InstructionSet_Opcodes.cc: InstructionSet.opcode makeOpcodes.pl perl $(srcdir)/makeOpcodes.pl $< -#package: clean -# git commit -a -# git tag -a -f $(VERSION) - diff --git a/MemoryMap.cc b/MemoryMap.cc index 3d8df1b..290c73d 100644 --- a/MemoryMap.cc +++ b/MemoryMap.cc @@ -60,7 +60,23 @@ MemoryMap::map( TraceMem() << "Registering " << glBoy::hex(base) << - " for " << glBoy::hex(size) << " bytes."<< Log::endl; + " for " << glBoy::hex(size) << " bytes." << Log::endl; +} + +uint16_t +MemoryMap::getBankIndex(DWORD address) const +{ + uint16_t offsetAddress(address.host()); + const MappedArea& mapping(get(offsetAddress)); + return mapping.memory->getBankIndex(offsetAddress - mapping.base); +} + +uint16_t +MemoryMap::getBankSize(DWORD address) const +{ + uint16_t offsetAddress(address.host()); + const MappedArea& mapping(get(offsetAddress)); + return mapping.memory->getBankSize(offsetAddress - mapping.base); } void @@ -199,11 +215,24 @@ MemoryMap::copy(uint8_t* dest, uint16_t srcAddress, int bytes) { const MappedArea& srcMap(get(srcAddress)); - assert((srcAddress + bytes) <= (srcMap.base + srcMap.size)); - - srcMap.memory->copy(dest, srcAddress - srcMap.base, bytes); + uint16_t offset(srcAddress - srcMap.base); + uint16_t copyBytes(std::min(srcMap.size - offset, bytes)); + srcMap.memory->copy(dest, offset, copyBytes); } +// Currently only used for loading saved games. +void +MemoryMap::copy(uint16_t destAddress, uint8_t* src, int bytes) +{ + const MappedArea& destMap(get(destAddress)); + + uint16_t offset(destAddress - destMap.base); + + for (int i = 0; i < bytes && i < (destMap.size - offset); ++i) + { + destMap.memory->write8(offset + i, src[i]); + } +} void MemoryMap::Memory::copy(uint8_t* dest, uint16_t address, int bytes) { diff --git a/MemoryMap.hh b/MemoryMap.hh index e01041c..bcb09bd 100644 --- a/MemoryMap.hh +++ b/MemoryMap.hh @@ -38,6 +38,9 @@ public: virtual uint8_t read8(uint16_t address) = 0; virtual void write8(uint16_t address, uint8_t value) = 0; virtual void copy(uint8_t* dest, uint16_t address, int bytes); + + virtual uint16_t getBankIndex(uint16_t) const { return 0; } + virtual uint16_t getBankSize(uint16_t) const { return 0; } }; MemoryMap(); @@ -50,6 +53,9 @@ public: void remove(uint16_t base); + uint16_t getBankIndex(DWORD address) const; + uint16_t getBankSize(DWORD address) const; + uint8_t read8(DWORD address, int8_t offset = 0); void write8(DWORD address, uint8_t value); void write8(DWORD address, int8_t offset, uint8_t value); @@ -59,14 +65,20 @@ public: void write16(DWORD address, int8_t offset, DWORD value); void copy(uint8_t* dest, uint16_t srcAddress, int bytes); + void copy(uint16_t destAddress, uint8_t* src, int bytes); private: struct MappedArea { MappedArea( - uint16_t _base, uint16_t _size, std::shared_ptr _mem + uint16_t _base, + uint16_t _size, + std::shared_ptr _mem ) : - base(_base), size(_size), memory(_mem) {} + base(_base), + size(_size), + memory(_mem) + {} uint16_t base; uint16_t size; diff --git a/NEWS b/NEWS index 2483f02..e93883f 100644 --- a/NEWS +++ b/NEWS @@ -4,8 +4,10 @@ settings. - Improved MBC1 support. - Added MBC2 and MBC3 support. + - Added saved game support. - Added compressed ROM support (zip and gzip). - Numerous z80 emulation bugfixes. + - Fixed GameBoy CPU flags (only high 4bits supported, unlike z80) 2011-01-31 Version 1.0.0 - Initial release diff --git a/RAM.cc b/RAM.cc index 400eb33..0005c45 100644 --- a/RAM.cc +++ b/RAM.cc @@ -30,19 +30,32 @@ RAM::RAM(uint16_t size) uint8_t RAM::read8(uint16_t address) { - return m_mem[address]; + if (address < m_mem.size()) + { + return m_mem[address]; + } + else + { + return 0; + } } void RAM::write8(uint16_t address, uint8_t value) { - m_mem[address] = value; + if (address < m_mem.size()) + { + m_mem[address] = value; + } } void RAM::copy(uint8_t* dest, uint16_t address, int bytes) { - memcpy(dest, &m_mem[0] + address, bytes); + memcpy( + dest, + &m_mem[0] + address, + std::min(bytes, int(m_mem.size() - address))); } diff --git a/RAM.hh b/RAM.hh index 636b5dd..10b25c7 100644 --- a/RAM.hh +++ b/RAM.hh @@ -33,7 +33,6 @@ public: virtual void write8(uint16_t address, uint8_t value); virtual void copy(uint8_t* dest, uint16_t address, int bytes); - private: bool m_fourBitMode; std::vector m_mem; diff --git a/RAM_4bit.cc b/RAM_4bit.cc index ca4db00..62559ab 100644 --- a/RAM_4bit.cc +++ b/RAM_4bit.cc @@ -31,19 +31,32 @@ RAM_4bit::RAM_4bit(uint16_t size) uint8_t RAM_4bit::read8(uint16_t address) { - return m_mem[address] & 0xF; + if (address < m_mem.size()) + { + return m_mem[address] & 0xF; + } + else + { + return 0; + } } void RAM_4bit::write8(uint16_t address, uint8_t value) { - m_mem[address] = value & 0xF; + if (address < m_mem.size()) + { + m_mem[address] = value & 0xF; + } } void RAM_4bit::copy(uint8_t* dest, uint16_t address, int bytes) { - memcpy(dest, &m_mem[0] + address, bytes); + memcpy( + dest, + &m_mem[0] + address, + std::min(bytes, int(m_mem.size() - address))); } diff --git a/Registers.cc b/Registers.cc index 1f83436..0ce7ea9 100644 --- a/Registers.cc +++ b/Registers.cc @@ -25,40 +25,40 @@ using namespace glBoy; void -Flags::set(op8_t operand, const Core& context) +Flags::set(op8_t operand, const Core& /*context*/) { - S = static_cast(operand) < 0 ? 1 : 0; + //S = static_cast(operand) < 0 ? 1 : 0; Z = operand == 0 ? 1 : 0; - U5 = (operand & 0b00010000) >> 4; + //U5 = (operand & 0b00010000) >> 4; H = 0; - U3 = (operand & 0b00000100) >> 2; - P = context.getIFF2(); + //U3 = (operand & 0b00000100) >> 2; + //P = context.getIFF2(); N = 0; // C is not affected } void -Flags::setCounter(DWORD BC) +Flags::setCounter(DWORD /*BC*/) { // S is not affected // Z is not affected - U5 = (BC.host() & 0b00010000) >> 4; + //U5 = (BC.host() & 0b00010000) >> 4; H = 0; - U3 = (BC.host() & 0b00000100) >> 2; - P = BC.host() ? 1 : 0; + //U3 = (BC.host() & 0b00000100) >> 2; + //P = BC.host() ? 1 : 0; N = 0; // C is not affected } void -Flags::setSub(op8_t A, op8_t B, DWORD BC) +Flags::setSub(op8_t A, op8_t B, DWORD /*BC*/) { - S = (A > B) ? 1 : 0; + //S = (A > B) ? 1 : 0; Z = (A == B) ? 1 : 0; - U5 = (A & 0b00010000) >> 4; + //U5 = (A & 0b00010000) >> 4; H = (B & 0xF) > (A & 0xF); - U3 = (A & 0b00000100) >> 2; - P = BC.host() ? 1 : 0; + //U3 = (A & 0b00000100) >> 2; + //P = BC.host() ? 1 : 0; N = 1; // C is not affected } diff --git a/Registers.hh b/Registers.hh index 4f460cb..23b43cb 100644 --- a/Registers.hh +++ b/Registers.hh @@ -43,14 +43,14 @@ struct __attribute__ ((__packed__)) Flags { struct __attribute__ ((__packed__)) { - unsigned C:1; /// Carry. LSB of Flag register - unsigned N:1; /// Add/Subtract - unsigned P:1; /// (V) Parity/Overflow - unsigned U3:1; /// 3rd bit of last 8bit op that altered flags + unsigned U1:1; // LSB + unsigned U2:1; + unsigned U3:1; + unsigned U4:1; + unsigned C:1; /// Carry. unsigned H:1; /// Half-Carry (BCD) - unsigned U5:1; /// 5th bit of last 8bit op that altered flags + unsigned N:1; /// Add/Subtract unsigned Z:1; /// Zero Flag - unsigned S:1; /// Sign Flag. MSB of Flag register }; reg8_t byte; }; @@ -86,11 +86,6 @@ struct Registers h; \ }; - // Note: The order of these unions/structs is important, as - // offsets are taken into the Register struct - reg8_t begin8[0]; - DWORD begin16[0]; - union { Z80_REG_STRUCT(reg8_t B, reg8_t C); diff --git a/Test.cc b/Test.cc deleted file mode 100644 index 458b8a2..0000000 --- a/Test.cc +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (C) 2011 Michael McMaster -// -// This file is part of glBoy. -// -// glBoy is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// glBoy is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with glBoy. If not, see . - -#include "ALU.hh" -#include "DAA.hh" - -#include "stddef.h" - -#include -#include -#include - -using namespace glBoy; - -void add(uint8_t a, uint8_t b) -{ - Flags f; - f.reset(); - uint8_t result(ALU::add(a, b, f)); - - uint16_t testFlags; - uint8_t testResult; - __asm__ ( - "movb %2, %%al\n\t" - "addb %3, %%al\n\t" - "pushf\n\t" - "mov %%al, %1\n\t" - "pop %%ax\n\t" - "movw %%ax, %0\n\t" - : "=g" (testFlags), "=g" (testResult) - : "g" (a), "g" (b) - : "eax", "cc" - ); - - assert(result == testResult); - assert(f.S == (testFlags & 0x80) >> 7); - assert(f.Z == (testFlags & 0x40) >> 6); - assert(f.H == (testFlags & 0x10) >> 4); - assert(f.P == (testFlags & 0x800) >> 11); // 0x4 for parity - assert(f.C == (testFlags & 0x1)); -} - -void sub(uint8_t a, uint8_t b) -{ - Flags f; - f.reset(); - uint8_t result(ALU::sub(a, b, f)); - - uint16_t testFlags; - uint8_t testResult; - __asm__ ( - "movb %2, %%al\n\t" - "subb %3, %%al\n\t" - "pushf\n\t" - "mov %%al, %1\n\t" - "pop %%ax\n\t" - "movw %%ax, %0\n\t" - : "=g" (testFlags), "=g" (testResult) - : "g" (a), "g" (b) - : "eax", "cc" - ); - - assert(result == testResult); - assert(f.S == (testFlags & 0x80) >> 7); - assert(f.Z == (testFlags & 0x40) >> 6); - assert(f.H == (testFlags & 0x10) >> 4); - assert(f.P == (testFlags & 0x800) >> 11); // 0x4 for parity - assert(f.C == (testFlags & 0x1)); -} - -void bitwiseAnd(uint8_t a, uint8_t b) -{ - Flags f; - f.reset(); - uint8_t result(ALU::bitwiseAnd(a, b, f)); - - uint16_t testFlags; - uint8_t testResult; - __asm__ ( - "movb %2, %%al\n\t" - "andb %3, %%al\n\t" - "pushf\n\t" - "mov %%al, %1\n\t" - "pop %%ax\n\t" - "movw %%ax, %0\n\t" - : "=g" (testFlags), "=g" (testResult) - : "g" (a), "g" (b) - : "eax", "cc" - ); - - assert(result == testResult); - assert(f.S == (testFlags & 0x80) >> 7); - assert(f.Z == (testFlags & 0x40) >> 6); - //assert(f.H == (testFlags & 0x10) >> 4); - // H is always set for AND for Z80, but not X86 - assert(f.H); - assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow - assert(f.C == (testFlags & 0x1)); -} - -void bitwiseOr(uint8_t a, uint8_t b) -{ - Flags f; - f.reset(); - uint8_t result(ALU::bitwiseOr(a, b, f)); - - uint16_t testFlags; - uint8_t testResult; - __asm__ __volatile__ ( - "movb %2, %%al\n\t" - "orb %3, %%al\n\t" - "pushf\n\t" - "mov %%al, %1\n\t" - "pop %%ax\n\t" - "movw %%ax, %0\n\t" - : "=g" (testFlags), "=g" (testResult) - : "g" (a), "g" (b) - : "eax", "cc" - ); - - assert(result == testResult); - assert(f.S == (testFlags & 0x80) >> 7); - assert(f.Z == (testFlags & 0x40) >> 6); - assert(f.H == (testFlags & 0x10) >> 4); - assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow - assert(f.C == (testFlags & 0x1)); -} - -void bitwiseXor(uint8_t a, uint8_t b) -{ - Flags f; - f.reset(); - uint8_t result(ALU::bitwiseXor(a, b, f)); - - uint16_t testFlags; - uint8_t testResult; - __asm__ __volatile__( - "movb %2, %%al\n\t" - "xorb %3, %%al\n\t" - "pushf\n\t" - "mov %%al, %1\n\t" - "pop %%ax\n\t" - "movw %%ax, %0\n\t" - : "=g" (testFlags), "=g" (testResult) - : "g" (a), "g" (b) - : "eax", "cc" - ); - - assert(result == testResult); - assert(f.S == (testFlags & 0x80) >> 7); - assert(f.Z == (testFlags & 0x40) >> 6); - assert(f.H == (testFlags & 0x10) >> 4); - assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow - assert(f.C == (testFlags & 0x1)); -} - -uint8_t daa(uint8_t a, uint8_t b) -{ - Flags f; - f.reset(); - uint8_t tmp(ALU::add(a, b, f)); - - return DAA(tmp, f); - -} - -uint8_t daaSub(uint8_t a, uint8_t b) -{ - Flags f; - f.reset(); - uint8_t tmp(ALU::sub(a, b, f)); - - return DAA(tmp, f); - -} - -int main() -{ - // Ensure packed representations are the expected size, - // and usable within union types. - assert(sizeof(DWORD) == 2); - assert(sizeof(Flags) == 1); - - // Ensure 16bit registers are aligned to 16-bit boundaries. - assert(offsetof(Registers, BC) == 0); - assert(offsetof(Registers, AF) == 6); - - add(0, 0); - add(1, 1); - add(-1, 1); - add(-128, 127); - add(127, 1); - add(-128, -1); - add(5, 5); - add(15, 1); - add(15, 128); - - Flags f; - f.reset(); - assert(ALU::add(htoz(0x1000), htoz(0x5555), f).host() == 0x6555); - assert(!f.C); - assert(ALU::add(htoz(0x1000), htoz(0xF555), f).host() == 0x0555); - assert(f.C); - - - sub(0, 0); - sub(1, 1); - sub(-1, 1); - sub(-128, 127); - sub(127, 1); - sub(-128, -1); - sub(5, 5); - sub(15, 1); - sub(15, 128); - sub(0, 0xb); - - bitwiseAnd(0, 0); - bitwiseAnd(1, 1); - bitwiseAnd(-1, 1); - bitwiseAnd(-128, 127); - bitwiseAnd(127, 1); - bitwiseAnd(-128, -1); - bitwiseAnd(5, 5); - bitwiseAnd(15, 1); - bitwiseAnd(15, 128); - - bitwiseOr(0, 0); - bitwiseOr(1, 1); - bitwiseOr(-1, 1); - bitwiseOr(-128, 127); - bitwiseOr(127, 1); - bitwiseOr(-128, -1); - bitwiseOr(5, 5); - bitwiseOr(15, 1); - bitwiseOr(15, 128); - - bitwiseXor(0, 0); - bitwiseXor(1, 1); - bitwiseXor(-1, 1); - bitwiseXor(-128, 127); - bitwiseXor(127, 1); - bitwiseXor(-128, -1); - bitwiseXor(5, 5); - bitwiseXor(15, 1); - bitwiseXor(15, 128); - - assert(daa(0x01, 0x01) == 0x02); - assert(daa(0x01, 0x09) == 0x10); - assert(daa(0x01, 0x10) == 0x11); - assert(daa(0x09, 0x09) == 0x18); - assert(daa(0x50, 0x49) == 0x99); - assert(daa(0x99, 0x0) == 0x99); - assert(daa(0x27, 0x27) == 0x54); - assert(daa(0x90, 0x10) == 0x0); // Carry from 0x100 - assert(daa(0x99, 0x99) == 0x98); // Carry from 0x198 - - assert(daaSub(0x10, 0x01) == 0x09); - assert(daaSub(0x10, 0x20) == 0x90); - - std::cout << "Tests complete" << std::endl; -} diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 0000000..2d31089 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk new file mode 100644 index 0000000..ccd788d --- /dev/null +++ b/android/jni/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_CPP_EXTENSION := .cc + +LOCAL_LDLIBS := -llog -lz +LOCAL_STATIC_LIBRARIES := libzipper + +LOCAL_MODULE := glBoy + +LOCAL_SRC_FILES :=\ + AndroidInterface.cc \ + ../../Core.cc \ + ../../FPS.cc \ + ../../GameboyCart.cc \ + ../../GameboyGraphics.cc \ + ../../GameboyJoypad.cc \ + ../../GameboySound.cc \ + ../../GameboyTimer.cc \ + ../../Log.cc \ + ../../MBC1.cc \ + ../../MBC2.cc \ + ../../MBC3.cc \ + ../../MemoryMap.cc \ + ../../RAM.cc \ + ../../RAM_4bit.cc \ + ../../Registers.cc \ + ../../ROM.cc \ + +include $(BUILD_SHARED_LIBRARY) + +$(call import-module,zipper) + diff --git a/android/jni/AndroidInterface.cc b/android/jni/AndroidInterface.cc new file mode 100644 index 0000000..fd373fd --- /dev/null +++ b/android/jni/AndroidInterface.cc @@ -0,0 +1,212 @@ +#include +#include +#include + +#include "../../Core.hh" +#include "../../GameboyCart.hh" +#include "../../GameboyGraphics.hh" +#include "../../GameboyJoypad.hh" +#include "../../GameboySound.hh" +#include "../../GameboyTimer.hh" +#include "../../RAM.hh" + +using namespace glBoy; + +#define DEBUG_TAG "libglBoy.so" + +struct State +{ + Core* m_core; + GameboyCart* m_cart; + GameboyGraphics* m_graphics; + pthread_t m_thread; +} GlobalState = {0, 0, 0, 0}; + +extern "C" +{ + +static void* start(void* val) +{ + Core* core = reinterpret_cast(val); + core->run(); + return NULL; +} + +JNIEXPORT jboolean JNICALL Java_com_codesrc_glboy_GlBoyActivity_loadCart( + JNIEnv * j_env, + jobject /*this_*/, + jstring j_filename) +{ + jboolean result = false; + + std::string filename; + { + jboolean isCopy; + const char* filenameStr = j_env->GetStringUTFChars(j_filename, &isCopy); + filename = filenameStr; + j_env->ReleaseStringUTFChars(j_filename, filenameStr); + } + + __android_log_print( + ANDROID_LOG_DEBUG, DEBUG_TAG, "Opening cart: %s", filename.c_str()); + + try + { + GlobalState.m_core = new Core(); + Core& core = *GlobalState.m_core; + GlobalState.m_cart = new GameboyCart(core, filename); + + if (!GlobalState.m_cart->isValid()) + { + throw GameboyCart::Invalid("Unknown"); + } + + __android_log_print( + ANDROID_LOG_DEBUG, DEBUG_TAG, "Cart opened."); + + GlobalState.m_graphics = new GameboyGraphics(core); + + // Map the standard internal RAM, plus shadow. + MemoryMap& map(core.getMemoryMap()); + { + std::shared_ptr mem(new RAM(8192)); + map.map(0xC000, 8192, mem); + map.map(0xE000, 7680, mem); + } + + // Map the "zero-page" high-speed internal ram. + // Most interaction between GB hardware and the program occurs here. + // Exclude 0xFFFF, which is handled directly within Core for interrupt + // handling + { + std::shared_ptr mem(new RAM(127)); + map.map(0xFF80, 127, mem); + } + + // Map other devices + std::shared_ptr joypad( + new GameboyJoypad(core) + ); + map.map(0xFF00, 1, joypad); + std::shared_ptr timer( + new GameboyTimer( + core, + std::bind( + &GameboyGraphics::setFrameSkip, GlobalState.m_graphics), + std::bind( + &GameboyGraphics::clearFrameSkip, GlobalState.m_graphics) + ) + ); + map.map(0xFF04, 4, timer); + std::shared_ptr sound(new GameboySound(core)); + map.map(0xFF10, 48, sound); + + // Cart starts from offset 0x100 with a NOP; JP + core.getRegisters().PC = glBoy::htoz(0x100); + core.getRegisters().SP = glBoy::htoz(0xFFFE); + core.getRegisters().AF = glBoy::htoz(0x01B0); + core.getRegisters().BC = glBoy::htoz(0x0013); + core.getRegisters().DE = glBoy::htoz(0x00D8); + core.getRegisters().HL = glBoy::htoz(0x014D); + + // Run the core in a new thread. + // OpenGL output must be in the main thread. + pthread_create(&GlobalState.m_thread, NULL, start, &core); + + result = true; + } + catch (std::exception& e) + { + __android_log_print( + ANDROID_LOG_DEBUG, DEBUG_TAG, "Exception caught: %s", e.what()); + + // Don't worry about m_thread, as exceptions can't be thrown after it is + // created. + delete GlobalState.m_graphics; + delete GlobalState.m_cart; + delete GlobalState.m_core; + GlobalState.m_graphics = 0; + GlobalState.m_cart = 0; + GlobalState.m_core = 0; + } + + return result; +} + +JNIEXPORT void JNICALL Java_com_codesrc_glboy_GlBoyActivity_stop( + JNIEnv* /*j_env*/, + jobject /*this_*/) +{ + __android_log_print( + ANDROID_LOG_DEBUG, DEBUG_TAG, "Requested to stop"); + + if (GlobalState.m_core) + { + GlobalState.m_core->stop(); + } + + if (GlobalState.m_thread) + { + pthread_join(GlobalState.m_thread, NULL); + } + delete GlobalState.m_graphics; + delete GlobalState.m_cart; + delete GlobalState.m_core; + GlobalState.m_graphics = 0; + GlobalState.m_cart = 0; + GlobalState.m_core = 0; + GlobalState.m_thread = 0; +} + +JNIEXPORT void JNICALL Java_com_codesrc_glboy_GlBoyActivity_pause( + JNIEnv* /*j_env*/, + jobject /*this_*/) +{ + __android_log_print( + ANDROID_LOG_DEBUG, DEBUG_TAG, "Requested to pause"); + if (GlobalState.m_core) + { + GlobalState.m_core->stop(); + } + if (GlobalState.m_thread) + { + pthread_join(GlobalState.m_thread, NULL); + } + GlobalState.m_thread = 0; +} + +JNIEXPORT void JNICALL Java_com_codesrc_glboy_GlBoyActivity_resume( + JNIEnv* /*j_env*/, + jobject /*this_*/) +{ + __android_log_print( + ANDROID_LOG_DEBUG, DEBUG_TAG, "Requested to resume"); + + // Run the core in a new thread. + // OpenGL output must be in the main thread. + if (!GlobalState.m_thread && GlobalState.m_core) + { + GlobalState.m_core->resume(); + pthread_create(&GlobalState.m_thread, NULL, start, GlobalState.m_core); + } +} + +JNIEXPORT void JNICALL Java_com_codesrc_glboy_GlBoyActivity_getFrame( + JNIEnv * j_env, + jobject /*this_*/, + jbyteArray frameBuffer) +{ + if (GlobalState.m_graphics) + { + uint8_t* rawFrame = static_cast( + j_env->GetPrimitiveArrayCritical(frameBuffer, 0)); + GlobalState.m_graphics->getGreyscaleFrame(rawFrame); // thread safe + j_env->ReleasePrimitiveArrayCritical(frameBuffer, rawFrame, 0); + } +} + + +} // extern "C" + + + diff --git a/android/jni/Application.mk b/android/jni/Application.mk new file mode 100644 index 0000000..7a6ef43 --- /dev/null +++ b/android/jni/Application.mk @@ -0,0 +1,14 @@ +APP_CPPFLAGS += -fexceptions -frtti +APP_STL := gnustl_static +APP_MODULES := zipper glBoy + +# "Fat" binary. Let the installer choose which is appropriate. +APP_ABI := armeabi armeabi-v7a + +# Yes, the NDK has confused the meaning of CPPFLAGS vs CXXFLAGS :-( +APP_CFLAGS := -W -Wall -Werror -D_POSIX_C_SOURCE=200112 -UNDEBUG + +# bionic /sys/types.h fails with -std=c++0x, as it doesn't include +# stdint.h, but tries to use uint64_t. +APP_CPPFLAGS := -std=gnu++0x + diff --git a/android/jni/autoconfig.h b/android/jni/autoconfig.h new file mode 100644 index 0000000..ac0a8d7 --- /dev/null +++ b/android/jni/autoconfig.h @@ -0,0 +1,75 @@ +/* autoconfig.h. Generated from autoconfig.h.in by configure. */ +/* autoconfig.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Compile for big-endian host cpu */ +/* #undef HOST_BIG_ENDIAN */ + +/* Compile for litte-endian host cpu */ +#define HOST_LITTLE_ENDIAN /**/ + + +/* Name of package */ +#define PACKAGE "glboy" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "michael@codesrc.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "glBoy" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "glBoy 1.0.1" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "glboy" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.0.1" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "1.0.1" + +/* Android-specific dodgy c-library hacks */ +/* http://code.google.com/p/android/issues/detail?id=20140 */ +#include +extern "C" +{ +extern int clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec*); +} + diff --git a/android/src/com/codesrc/glboy/GlBoyActivity.java b/android/src/com/codesrc/glboy/GlBoyActivity.java new file mode 100644 index 0000000..bd769f6 --- /dev/null +++ b/android/src/com/codesrc/glboy/GlBoyActivity.java @@ -0,0 +1,403 @@ +package com.codesrc.glboy; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + + +import android.app.Activity; + +import android.os.Bundle; +import android.opengl.GLSurfaceView; +import android.opengl.GLES20; +import android.opengl.Matrix; +import android.view.MotionEvent; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; + +public class GlBoyActivity extends Activity +{ + static { + System.loadLibrary("glBoy"); + } + + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + stop(); + //mGLView = new GameboyView(this); + //setContentView(mGLView); + + setContentView(R.layout.main); + mGLView = (GLSurfaceView) findViewById(R.id.glsurfaceview); + mGLView.setEGLContextClientVersion(2); // GLES 2.0 + mGLView.setRenderer(new GameboyRenderer(this)); + + // Find our buttons + // Button aButton = (Button) findViewById(R.id.buttonA); + + // Wire each button to a click listener + // aButton.setOnTouchListener(OnTouchListener) + // aButton.set.setOnClickListener(mVisibleListener); + + + } + /* + OnClickListener mInvisibleListener = new OnTouchListener() { + public void onClick(View v) { + mVictim1.setVisibility(View.INVISIBLE); + mVictim2.setVisibility(View.INVISIBLE); + mVictimContainer.setVisibility(View.INVISIBLE); + } + };*/ + + @Override + public void onStart() + { + super.onStart(); + loadCart("/sdcard/tetris.zip"); + } + + @Override + public void onStop() + { + super.onStop(); + stop(); + } + + @Override + public void onPause() + { + super.onPause(); + pause(); + mGLView.onPause(); + } + + @Override + public void onResume() + { + super.onResume(); + resume(); + mGLView.onResume(); + } + + private native boolean loadCart(String filename); + private native void pause(); + private native void resume(); + private native void stop(); + public native void getFrame(byte[] framebuffer); + + private GLSurfaceView mGLView; +} + +class GameboyView extends GLSurfaceView +{ + public GameboyView(GlBoyActivity context) + { + super(context); + setEGLContextClientVersion(2); // GLES 2.0 + mRenderer = new GameboyRenderer(context); + setRenderer(mRenderer); + + // setDebugFlags(GLSurfaceView.DEBUG_CHECK_GL_ERROR | GLSurfaceView.DEBUG_LOG_GL_CALLS); + } + + public boolean onTouchEvent(final MotionEvent event) { + /* + queueEvent(new Runnable(){ + public void run() { + mRenderer.setColor(event.getX() / getWidth(), + event.getY() / getHeight(), 1.0f); + }}); + */ + return true; + } + + GameboyRenderer mRenderer; +} + +class GameboyRenderer implements GLSurfaceView.Renderer +{ + GameboyRenderer(GlBoyActivity context) + { + m_activity = context; + m_frameBuffer = ByteBuffer.wrap(m_frameBufferBytes); + + } + public void onSurfaceCreated(GL10 obsolete, EGLConfig config) + { + m_program = createProgram(m_vertexShader, m_fragmentShader); + if (m_program == 0) { + return; + } + + m_aPosition = GLES20.glGetAttribLocation(m_program, "a_position"); + checkGlError("glGetAttribLocation a_position"); + if (m_aPosition == -1) { + throw new RuntimeException("Could not get attrib location for a_position"); + } + + m_aTexCoord = GLES20.glGetAttribLocation(m_program, "a_texCoord"); + checkGlError("glGetAttribLocation a_texCoord"); + if (m_aTexCoord == -1) { + throw new RuntimeException("Could not get attrib location for a_texCoord"); + } + + m_sTexture = GLES20.glGetUniformLocation(m_program, "s_texture"); + checkGlError("glGetUniformLocation s_texture"); + if (m_sTexture == -1) { + throw new RuntimeException("Could not get uniform location for s_texture"); + } + + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + + // Allocate texture + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + m_textureId = textures[0]; + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureId); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); // Must be called before glTexParameterf + + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + + // Clamp-to-edge required for non-power-of-two texture + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE); + + ByteBuffer byteBuf = ByteBuffer.allocateDirect(m_vertices.length * 4); + byteBuf.order(ByteOrder.nativeOrder()); + m_vertexBuffer = byteBuf.asFloatBuffer(); + m_vertexBuffer.put(m_vertices); + m_vertexBuffer.position(0); + + ByteBuffer byteBuf2 = ByteBuffer.allocateDirect(m_textureCoords.length * 4); + byteBuf2.order(ByteOrder.nativeOrder()); + m_textureBuffer = byteBuf2.asFloatBuffer(); + m_textureBuffer.put(m_textureCoords); + m_textureBuffer.position(0); + } + + public void onSurfaceChanged(GL10 obsolete, int w, int h) + { + float aspect = 144.0f/160.f; + if (h > w) + { + int gameHeight = (int)(w*aspect); + GLES20.glViewport(0, h - gameHeight, w, gameHeight); + } + else + { + int gameWidth = (int)(h/aspect); + GLES20.glViewport((w - gameWidth)/2, 0, gameWidth, h); + } + + // this projection matrix is applied to object coodinates + // in the onDrawFrame() method + // Camera is at (0,0,2), looking down the -z axis, + // with (0,1,0) "up" vector. + // The near and far values supplied to glOrthof define the -distance- + // of the clipping frames from the camera. ie. near/far of 1, 10 + // will only draw points with a z value between 1 and -8. + Matrix.orthoM(m_ProjMatrix, 0, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 10.0f); + m_uMVPMatrixHandle = GLES20.glGetUniformLocation(m_program, "uMVPMatrix"); + + // Change the "view" matrix to set everything to z = -1, so it is within + // our clipping planes. + Matrix.setLookAtM(m_VMatrix, 0, 0, 0, 2, 0f, 0f, 0f, 0f, 1.0f, 0.0f); + + // Our textures increase Y when heading down. This is the opposite + // of the opengl coordinates, so we'll flip it here. + Matrix.setIdentityM(m_MMatrix, 0); + Matrix.scaleM(m_MMatrix, 0, 1.0f, -1.0f, 1.0f); + + // May as well MVP here, since it doesn't change. + Matrix.multiplyMM(m_MVPMatrix, 0, m_VMatrix, 0, m_MMatrix, 0); + Matrix.multiplyMM(m_MVPMatrix, 0, m_ProjMatrix, 0, m_MVPMatrix, 0); + } + + public void onDrawFrame(GL10 obsolete) + { + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + GLES20.glUseProgram(m_program); + //checkGlError("glUseProgram"); + + m_activity.getFrame(m_frameBufferBytes); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, m_textureId); + + // Create the texture image + GLES20.glTexImage2D( + GLES20.GL_TEXTURE_2D, + 0, + GLES20.GL_LUMINANCE, + 160, + 144, + 0, + GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, + m_frameBuffer + ); + GLES20.glUniform1i(m_sTexture, 0); + + GLES20.glUniformMatrix4fv(m_uMVPMatrixHandle, 1, false, m_MVPMatrix, 0); + + m_vertexBuffer.position(0); + GLES20.glVertexAttribPointer( + m_aPosition, + 3, // x, y, z + GLES20.GL_FLOAT, + false, + 0, // Tightly packed + m_vertexBuffer); + GLES20.glEnableVertexAttribArray(m_aPosition); + + m_textureBuffer.position(0); + GLES20.glVertexAttribPointer( + m_aTexCoord, + 2, // x, y + GLES20.GL_FLOAT, + false, + 0, // Tightly packed + m_textureBuffer); + GLES20.glEnableVertexAttribArray(m_aTexCoord); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + //checkGlError("glDrawArrays"); + } + + private int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + if (shader != 0) { + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + } + return shader; + } + + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + + int program = GLES20.glCreateProgram(); + if (program != 0) { + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + } + return program; + } + + private void checkGlError(String op) { + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(TAG, op + ": glError " + error); + throw new RuntimeException(op + ": glError " + error); + } + } + + private GlBoyActivity m_activity; + + // Native code fills this out for us, and we then display it using + // opengl + byte[] m_frameBufferBytes = new byte[144*160]; + ByteBuffer m_frameBuffer; + + private int m_textureId; + private int m_program; + private int m_aPosition; + private int m_aTexCoord; + private int m_sTexture; + private int m_uMVPMatrixHandle; + private float[] m_MVPMatrix = new float[16]; + private float[] m_VMatrix = new float[16]; + private float[] m_MMatrix = new float[16]; + private float[] m_ProjMatrix = new float[16]; + + private FloatBuffer m_vertexBuffer; + private FloatBuffer m_textureBuffer; // texture coords + private float m_vertices[] = + { + -1.0f, 1.0f, 0, // Top Left + 1.0f, 1.0f, 0, // Top Right + -1.0f, -1.0f, 0, // Bottom Left + 1.0f, -1.0f, 0 // Bottom Right + }; + private float m_textureCoords[] = + { + 0.0f, 1.0f, + 1.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f + }; + + private final String m_vertexShader = + "uniform mat4 uMVPMatrix; \n" + + "attribute vec4 a_position; \n" + + "attribute vec2 a_texCoord; \n" + + "varying vec2 c5; \n" + + "void main(){ \n" + + " gl_Position = uMVPMatrix * a_position; \n" + + " c5 = a_texCoord; \n" + + "}\n"; + + + private final String m_fragmentShader = + "precision mediump float;\n" + + "varying vec2 c5;\n" + + "uniform sampler2D s_texture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(s_texture, c5);\n" + + // " gl_FragColor = vec4(0.5, 0.3, 0.9, 1.0);\n" + + "}\n"; + private static String TAG = "glBoy Renderer"; +} diff --git a/glBoy.config b/glBoy.config index 5c8413c..7cca008 100644 --- a/glBoy.config +++ b/glBoy.config @@ -1,7 +1,6 @@ # glBoy example configuration file TODO list available keys here in a comment. -change name to ~/.glBoy/config as we need a directory for saved games as well. glBoy: { diff --git a/hq4x.cc b/hq4x.cc index 278011c..e0b0054 100644 --- a/hq4x.cc +++ b/hq4x.cc @@ -24,13 +24,8 @@ // - Applied clause 3 of LGPL 2.1 to alter license to GNU GPL 3. Updated // License notice at top of file as required by clause 3. -#define GL_GLEXT_PROTOTYPES - #include "hq4x.hh" -#include -#include - #include #include #include @@ -40,278 +35,8 @@ using namespace glBoy; -namespace -{ - -const char* -s_VectorSource = -"\ -//#version 130\n\ -\ -uniform vec4 mmTextureSize;\n\ -\ -varying vec4 c5;\n\ -\ -void main()\n\ -{\n\ - c5 = gl_MultiTexCoord0;\n\ -\ - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n\ -}\n\ -"; - -const char* -s_FragmentSource = -"\ -// Enable bitwise operators\n\ -#version 130\n\ -\n\ -#ifdef __GLSL_CG_DATA_TYPES\n\ -// NVidia specific\n\ -// These pragmas make a MASSIVE difference on some quadro cards.\n\ -// But no difference on my 6600.\n\ -#pragma optionNV(fastmath on)\n\ -#pragma optionNV(fastprecision on)\n\ -#pragma optionNV(ifcvt none)\n\ -#pragma optionNV(inline all)\n\ -#pragma optionNV(strict on)\n\ -#pragma optionNV(unroll all)\n\ -#endif\n\ -\n\ -\ -uniform sampler2D mmTexture;\n\ -uniform sampler2D mmLookup;\n\ -uniform vec4 mmTextureSize;\n\ -\ -in vec4 c5;\n\ -\ -out vec4 fragColour;\n\ -\ -vec4 RGB2YUV(in vec4 val)\n\ -{\n\ - return mat4(\ - 0.299, 0.587, 0.114, 0,\n\ - -0.14713, -0.28886, 0.436, 0,\n\ - 0.615, -0.51499, -0.10001, 0,\n\ - 0, 0, 0, 0\n\ - ) * val;\ -}\n\ -\ -bool Diff(in vec4 YUV1, in vec4 YUV2)\n\ -{\n\ - return any(\n\ - greaterThan(\n\ - abs(YUV1 - YUV2),\n\ - vec4(48.0/255.0, 7.0/255.0, 6.0/255.0, 0)\n\ - ).xyz\n\ - );\n\ -}\n\ -\ -void main()\n\ -{\n\ - vec4 c_offsetX = vec4(1.0/(mmTextureSize.x-1), 0, 0, 0);\n\ - vec4 c_offsetY = vec4(0, 1.0/(mmTextureSize.y-1), 0, 0);\n\ -\ - vec4 w[9];\n\ - w[0] = texture2DProj(mmTexture, c5 - c_offsetY - c_offsetX);\n\ - w[1] = texture2DProj(mmTexture, c5 - c_offsetY);\n\ - w[2] = texture2DProj(mmTexture, c5 - c_offsetY + c_offsetX);\n\ -\ - w[3] = texture2DProj(mmTexture, c5 - c_offsetX);\n\ - w[4] = texture2DProj(mmTexture, c5);\n\ - w[5] = texture2DProj(mmTexture, c5 + c_offsetX);\n\ -\ - w[6] = texture2DProj(mmTexture, c5 + c_offsetY - c_offsetX);\n\ - w[7] = texture2DProj(mmTexture, c5 + c_offsetY);\n\ - w[8] = texture2DProj(mmTexture, c5 + c_offsetY + c_offsetX);\n\ -\ - vec4 YUV[9];\n\ - YUV[0] = RGB2YUV(w[0]);\n\ - YUV[1] = RGB2YUV(w[1]);\n\ - YUV[2] = RGB2YUV(w[2]);\n\ - YUV[3] = RGB2YUV(w[3]);\n\ - YUV[4] = RGB2YUV(w[4]);\n\ - YUV[5] = RGB2YUV(w[5]);\n\ - YUV[6] = RGB2YUV(w[6]);\n\ - YUV[7] = RGB2YUV(w[7]);\n\ - YUV[8] = RGB2YUV(w[8]);\n\ -\ - int pattern = 0;\n\ - int flag = 1;\n\ - int k;\n\ - for (k = 0; k < 9; k++)\n\ - {\n\ - if (k == 4) continue;\n\ -\ - if (Diff(YUV[4], YUV[k]))\n\ - {\n\ - pattern = pattern + flag;\n\ - }\n\ - flag = flag * 2;\n\ - }\n\ -\ - // k is now diff.\n\ - k = 0;\n\ - if (Diff(YUV[1], YUV[5]))\n\ - {\n\ - k = k + 1;\n\ - }\n\ - if (Diff(YUV[5], YUV[7]))\n\ - {\n\ - k = k + 2;\n\ - }\n\ - if (Diff(YUV[7], YUV[3]))\n\ - {\n\ - k = k + 4;\n\ - }\n\ - if (Diff(YUV[3], YUV[1]))\n\ - {\n\ - k = k + 8;\n\ - }\n\ -\ - vec2 lookupMax = vec2(1.0/47.0, 1.0/4095);\n\ - ivec2 thisPixel = ivec2(fract(c5 * mmTextureSize) * 4.0);\n\ - ivec2 lookupIndex = ivec2((thisPixel.y*4+thisPixel.x)*3, (pattern * 16) + k);\n\ - vec4 weight1 = texture2D(mmLookup, lookupIndex * lookupMax);\n\ - vec4 weight2 = texture2D(mmLookup, (lookupIndex + ivec2(1, 0)) * lookupMax);\n\ - vec4 weight3 = texture2D(mmLookup, (lookupIndex + vec2(2, 0)) * lookupMax);\n\ -\ - // Dodgy fix for inaccurate lookups :-( Seemed to work on Quadro NVS 290 without this\n\ - float allW = dot(\n\ - vec4(1,1,1,0),\n\ - vec4(\n\ - dot(weight1, vec4(1,1,1,0)),\n\ - dot(weight2, vec4(1,1,1,0)),\n\ - dot(weight3, vec4(1,1,1,0)),\n\ - 0\n\ - )\n\ - );\n\ - fragColour =\n\ - w[0]*weight1.x + w[1]*weight1.y + w[2]*weight1.z +\n\ - w[3]*weight2.x + w[4]*weight2.y + w[5]*weight2.z +\n\ - w[6]*weight3.x + w[7]*weight3.y + w[8]*weight3.z;\n\ - fragColour = fragColour / allW;\n\ -\ -//if (allW > 1.100) { fragColour = vec4(0.0,0.0,1.0,0); } \n\ -//if (allW < 0.9) { fragColour = vec4(0.0,1.0,0.0,0); } \n\ -\ -}\n\ -"; - -void abortGLError() -{ - std::cerr << "Fatal OpenGL error in hq4x shader: " << - gluErrorString(glGetError()) << std::endl; - abort(); -} - -void abortShaderError(GLint shader) -{ - std::cerr << "Fatal error using hq4x shader: " << - gluErrorString(glGetError()) << std::endl; - - char buffer[2048]; - int len; - glGetShaderInfoLog(shader, 2048, &len, &buffer[0]); - std::cerr << buffer << std::endl; - - abort(); -} - -void abortProgramError(GLint program) +Hq4x::Hq4x() { - std::cerr << "Fatal error using hq4x program: " << - gluErrorString(glGetError()) << std::endl; - - char buffer[2048]; - int len; - glGetProgramInfoLog(program, 2048, &len, &buffer[0]); - std::cerr << buffer << std::endl; - - abort(); -} - -} // namespace - - -Hq4x::Hq4x(size_t textureWidth, size_t textureHeight) : - m_textureWidth(textureWidth), - m_textureHeight(textureHeight) -{ - m_vertexShader = glCreateShader(GL_VERTEX_SHADER); - m_fragShader = glCreateShader(GL_FRAGMENT_SHADER); - - if (!m_vertexShader || !m_fragShader) abortGLError(); - - glShaderSource(m_vertexShader, 1, &s_VectorSource, NULL); - glShaderSource(m_fragShader, 1, &s_FragmentSource, NULL); - - glCompileShader(m_vertexShader); - if (glGetError() != GL_NO_ERROR) abortShaderError(m_vertexShader); - glCompileShader(m_fragShader); - if (glGetError() != GL_NO_ERROR) abortShaderError(m_fragShader); - - m_program = glCreateProgram(); - if (!m_program) abortGLError(); - - glAttachShader(m_program, m_vertexShader); - if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); - glAttachShader(m_program, m_fragShader); - if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); - glLinkProgram(m_program); - if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); - - glGenTextures(1, &m_lookupTexture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m_lookupTexture); - - // Only nearest neighbour will work - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - // Create the texture image - glTexImage2D( - GL_TEXTURE_2D, - 0, - GL_RGBA, - 48, - 4096, - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - getHq4xLookupTable() - ); - glUseProgram(m_program); - if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); - - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, m_lookupTexture); - GLint tex = glGetUniformLocation(m_program, "mmLookup"); - glUniform1i(tex, 1); // Bind Texture 1 -} - -void -Hq4x::prepareShader(GLuint texture) -{ - glUseProgram(m_program); - if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); - - glBindFragDataLocationEXT(m_program,0,"fragColor"); - - GLint size(glGetUniformLocation(m_program, "mmTextureSize")); - glUniform4f(size, m_textureWidth, m_textureHeight, 0, 0); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - GLint tex(glGetUniformLocation(m_program, "mmTexture")); - glUniform1i(tex, 0); // Bind Texture 0 - -/* - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, m_lookupTexture); - tex = glGetUniformLocation(m_program, "mmLookup"); - glUniform1i(tex, 1); // Bind Texture 1 -*/ } //-------------------------------------------------- @@ -548,10 +273,10 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) -uint32_t* +Hq4x::Pixel* Hq4x::getHq4xLookupTable() const { - static uint32_t* s_table(0); + static Pixel* s_table(0); if (s_table) { @@ -559,7 +284,7 @@ Hq4x::getHq4xLookupTable() const } else { - s_table = new uint32_t[4096*48]; + s_table = new Pixel[4096*48]; for (int pattern = 0; pattern < 256; ++pattern) { @@ -5554,29 +5279,20 @@ Hq4x::getHq4xLookupTable() const ); -// TODO byteorder fixes here! - s_table[index] = - (w.w1 << 0) | - (w.w2 << 8) | - (w.w3 << 16); - - - s_table[index + 1] = - (w.w4 << 0) | - (w.w5 << 8) | - (w.w6 << 16); + s_table[index].r = w.w1; + s_table[index].g = w.w2; + s_table[index].b = w.w3; + s_table[index].a = 0; - s_table[index + 2] = - (w.w7 << 0) | - (w.w8 << 8) | - (w.w9 << 16); + s_table[index + 1].r = w.w4; + s_table[index + 1].g = w.w5; + s_table[index + 1].b = w.w6; + s_table[index + 1].a = 0; -//hmm, order appears to be ABGR -/* - s_table[index] = 0x0090FF10; - s_table[index+1] = 0x00; - s_table[index+2] = 0x00; -*/ + s_table[index + 2].r = w.w7; + s_table[index + 2].g = w.w8; + s_table[index + 2].b = w.w9; + s_table[index + 2].a = 0; } } } diff --git a/hq4x.hh b/hq4x.hh index 1242141..70cb4da 100644 --- a/hq4x.hh +++ b/hq4x.hh @@ -20,29 +20,24 @@ #include -#include - namespace glBoy { class Hq4x { public: - Hq4x(size_t textureWidth, size_t textureHeight); + Hq4x(); - void prepareShader(GLuint texture); + struct Pixel + { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + }; + Pixel* getHq4xLookupTable() const; private: enum Diff { Diff_2_6 = 1, Diff_6_8 = 2, Diff_8_4 = 4, Diff_4_2 = 8 }; - - uint32_t* getHq4xLookupTable() const; - - int m_textureWidth; - int m_textureHeight; - - GLint m_vertexShader; - GLint m_fragShader; - GLint m_program; - GLuint m_lookupTexture; }; } diff --git a/libzipper b/libzipper index f275ba4..b9f320b 160000 --- a/libzipper +++ b/libzipper @@ -1 +1 @@ -Subproject commit f275ba42be259b90a32a5b7f9399bc175c6e4c4e +Subproject commit b9f320b74352a88649f498af9a9fc608fa45d558 diff --git a/makeOpcodes.pl b/makeOpcodes.pl index d6f8847..d27bd5a 100644 --- a/makeOpcodes.pl +++ b/makeOpcodes.pl @@ -58,7 +58,7 @@ while ($node) } elsif ($reader->name eq "instruction") { - processInstruction($subtree) + expandInstruction($subtree) } else { @@ -133,31 +133,22 @@ sub processRegisterMask my $max = 1 << $maskBits; my $count = 0; - print TABLES "static size_t regMask_$name\[$max\] =\n{\n"; - - my @masks = (); + my %masks = (); while ($count < $max) { my $mask = sprintf("%0$maskBits"."b", $count); my $reg = $node->findvalue("./reg[\@mask='$mask']/\@name"); - if ($count != 0) { print TABLES ",\n"; } if ($reg && length $reg != 0) { - print TABLES "\toffsetof(glBoy::Registers, $reg) / $regBytes"; - push @masks, $mask; - } - else - { - print TABLES "\tsize_t(-1)"; + $masks{$mask} = $reg; } $count = $count + 1; } - print TABLES "\n};\n\n"; - $RegMasks{$name}{'masks'} = \@masks; + $RegMasks{$name}{'masks'} = \%masks; $RegMasks{$name}{'maskBits'} = $maskBits; $RegMasks{$name}{'regBytes'} = $regBytes; @@ -187,124 +178,110 @@ sub processBitMask $BitMasks{$name}{'maskBits'} = $maskBits; } -sub expandMask +sub expandInstruction { + my $node = shift; + my $name = shift; my $mask = shift; + my $code = shift; - my @result = (); + if (!$code) + { + $code = ""; + } - if ($mask =~ /([a-zA-Z])/) + if (!$name) + { + $name = $node->findvalue('./@name') || die; + } + my $clock = $node->findvalue('./@clock'); + if (!$mask) { - my $match = $1; + $mask = $node->findvalue('./opcode/@mask'); + } - if ($BitMasks{$match}) - { - foreach my $bits (@{$BitMasks{$match}{'masks'}}) - { - my $nextMask = $mask; - $nextMask =~ s/$match/$bits/; - push @result, expandMask($nextMask); - } - } - elsif ($RegMasks{$match}) - { - foreach my $bits (@{$RegMasks{$match}{'masks'}}) - { - my $nextMask = $mask; - $nextMask =~ s/$match/$bits/; - push @result, expandMask($nextMask); - } + my $prefix = $node->findvalue('./opcode/@prefix'); + my $operand = $node->findvalue('./opcode/@operand'); + my $signedOperand = $node->findvalue('./opcode/@signed_operand'); + my $variant = $node->findvalue('./@variant'); - } - $mask =~ s/$match/_/; + if ($mask !~ /[a-zA-Z]/) + { + $name =~ s/'//g; + processInstruction($node, $name, $mask, $code) } else { my $pos = 0; - my $value = 0; - - if (length $mask != 8) { croak "Bad Mask $mask\n"; } - while ($pos < 8) + my $bitPos = 0; + while ($pos < length $mask) { my $char = substr($mask, $pos, 1); - if ($char == 1) - { - $value = $value + (1 << (7 - $pos)); - } - $pos = $pos + 1; - } - push @result, $value; - } - return @result; -} - -sub createRegReference -{ - my $mask = shift; - - my %seenChars = (); - - my @dbgResult = (); - - # This is probably not the fastest way, but who cares. - my $pos = 0; - my $bitPos = 0; - while ($pos < length $mask) - { - my $char = substr($mask, $pos, 1); - if ($char =~ /[a-zA-Z]/ && $RegMasks{$char}) - { - my $outputName = $char; - if ($seenChars{$outputName}) - { - $seenChars{$outputName} = $seenChars{$outputName} + 1; - $outputName .= $seenChars{$outputName}; - } - else + if ($char =~ /[a-zA-Z]/ && $RegMasks{$char}) { - $seenChars{$outputName} = 1; + my $outputName = $char; + + my $maskBits = $RegMasks{$char}{'maskBits'}; + my $regBytes = $RegMasks{$char}{'regBytes'}; + + foreach my $bits (keys %{$RegMasks{$char}{'masks'}}) + { + my $reg = ${$RegMasks{$char}{'masks'}}{$bits}; + my $var = $char; + if($code =~ /uint8_t& $char/) + { + $var = $char."2"; + } + my $nextMask = $mask; + $nextMask =~ s/$char/$bits/; + my $nextName = $name; + $nextName =~ s/$char/$reg/; + my $nextCode = $code; + if ($regBytes == 1) + { + $nextCode .= "uint8_t& $var(m_reg.$reg);\n"; + } + else + { + $nextCode .= "DWORD& $var(m_reg.$reg);\n"; + } + + expandInstruction($node, $nextName, $nextMask, $nextCode); + } + last; } - - push @dbgResult, $outputName; - - my $maskBits = $RegMasks{$char}{'maskBits'}; - my $shiftNum = 8 - ($bitPos + $RegMasks{$char}{'maskBits'}); - if ($RegMasks{$char}{'regBytes'} == 1) + elsif ($char =~ /[a-zA-Z]/ && $BitMasks{$char}) { - print CODE "\tuint8_t& $outputName(m_reg.begin8[regMask_$char\[(opcode >> $shiftNum) & (uint8_t(-1) >> (8 - $maskBits)) \]\]);\n"; + my $maskBits = $BitMasks{$char}{'maskBits'}; + my $bitCount = 0; + foreach my $bits (@{$BitMasks{$char}{'masks'}}) + { + my $nextMask = $mask; + $nextMask =~ s/$char/$bits/; + my $nextName = $name; + $nextName =~ s/$char/$bitCount/; + my $nextCode = $code; + $nextCode .= "uint8_t $char($bitCount);\n"; + $bitCount = $bitCount + 1; + expandInstruction($node, $nextName, $nextMask, $nextCode); + } + + last; } - else - { - print CODE "\tDWORD& $outputName(m_reg.begin16[regMask_$char\[(opcode >> $shiftNum) & (uint8_t(-1) >> (8 - $maskBits)) \]\]);\n"; - } - - $bitPos = $bitPos + $maskBits; - } - elsif ($char =~ /[a-zA-Z]/ && $BitMasks{$char}) - { - my $maskBits = $BitMasks{$char}{'maskBits'}; - my $shiftNum = 8 - ($bitPos + $BitMasks{$char}{'maskBits'}); - print CODE "uint8_t $char((opcode >> $shiftNum) & (uint8_t(-1) >> (8 - $maskBits)));\n"; - $bitPos = $bitPos + $maskBits; - } - else - { - $bitPos = $bitPos + 1; + $pos = $pos + 1; } - - $pos = $pos + 1; } - return @dbgResult; } sub processInstruction { my $node = shift; + my $name = shift; + my $mask = shift; + my $initialCode = shift; - my $name = $node->findvalue('./@name') || die; my $clock = $node->findvalue('./@clock'); - my $mask = $node->findvalue('./opcode/@mask'); my $prefix = $node->findvalue('./opcode/@prefix'); my $operand = $node->findvalue('./opcode/@operand'); my $signedOperand = $node->findvalue('./opcode/@signed_operand'); @@ -325,12 +302,20 @@ sub processInstruction $label =~ tr/ ,'+()/______/; print CODE "$label:\n"; print CODE "{\n"; + print CODE $initialCode; + my $trace = "\tdbg << \"$name\t\";\n"; + if (length $name < 10) + { + $trace .= "\tdbg << \"\t\";\n"; + + } # Note: May have both signed operand (index offset) and operand if ($signedOperand && length $signedOperand == 1) { print CODE "\tint8_t $signedOperand(op8());\n"; + $trace .= "\tdbg << \" $signedOperand=\" << hex($signedOperand);\n"; } if ($operand && length $operand == 1) @@ -338,30 +323,21 @@ sub processInstruction if (length($prefix) > 4) { print CODE "\t uint8_t& $operand(doublePrefixOperand);\n"; + $trace .= "\tdbg << \" $operand=\" << hex($operand);\n"; } elsif (uc($operand) eq $operand) { print CODE "\tDWORD $operand(op16());\n"; + $trace .= "\tdbg << \" $operand=\" << $operand;\n"; } else { print CODE "\tuint8_t $operand(op8());\n"; + $trace .= "\tdbg << \" $operand=\" << hex($operand);\n"; } } - my @dbgRegs = createRegReference($mask); - - print CODE "//#ifdef GLBOY_DEBUG\n"; - print CODE "std::ostream& trace(std::cerr);//(log(Log::DBG_TRACE));\n". - "trace << ". - "hex(opcodePC) << \": \";\n". - "if (prefix) trace << hex(prefix) << \" \";\n". - "trace << hex(opcode) << \" $name\" << ". - ($operand ? "\" ($operand=\" << $operand << \")\" << " : "") . - ($dbgRegs[0] ? "\" ($dbgRegs[0]=\" << $dbgRegs[0] << \")\" << " : "") . - " \" \" << hex(m_reg.A) << ". - "std::endl;\n"; - print CODE "//#endif\n"; + print CODE "#ifdef GLBOY_DEBUG\n$trace\n#endif"; my $statements = $node->findvalue('./text()'); chomp($statements); @@ -370,22 +346,29 @@ sub processInstruction print CODE "\tclock += $clock;\n"; print "Processing $name\n"; - my @opcodes = expandMask($mask); if (!$prefix || length $prefix == 0) { $prefix = "default"; } - foreach my $opcode (@opcodes) + my $pos = 0; + my $value = 0; + if (length $mask != 8) { croak "Bad Mask $mask\n"; } + while ($pos < 8) { - if ($Instructions{$prefix}{$opcode}) + my $char = substr($mask, $pos, 1); + if ($char == 1) { - die "Duplicate opcode. Prefix = $prefix. Mask = $mask, $name.\n". - "Conflicting label = $Instructions{$prefix}{$opcode}\n"; + $value = $value + (1 << (7 - $pos)); } - $Instructions{$prefix}{$opcode} = $label; + $pos = $pos + 1; + } + if ($Instructions{$prefix}{$value}) + { + die "Duplicate opcode. Prefix = $prefix. Mask = $mask, $name.\n"; } + $Instructions{$prefix}{$value} = $label; print CODE "\n}\n"; print CODE "goto END_INSTRUCTIONS;\n"; diff --git a/sdl/GLGraphics.cc b/sdl/GLGraphics.cc new file mode 100644 index 0000000..0f74599 --- /dev/null +++ b/sdl/GLGraphics.cc @@ -0,0 +1,427 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#define GL_GLEXT_PROTOTYPES + +#include "glBoy.hh" +#include "GLGraphics.hh" +#include "Log.hh" + +#include +#include +#include +#include + +#include + +#include + +using namespace glBoy; + +namespace +{ + +const char* +s_VectorSource = +"\ +//#version 130\n\ +\ +uniform vec4 mmTextureSize;\n\ +\ +varying vec4 c5;\n\ +\ +void main()\n\ +{\n\ + c5 = gl_MultiTexCoord0;\n\ +\ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n\ +}\n\ +"; + +const char* +s_FragmentSource = +"\ +// Enable bitwise operators\n\ +#version 130\n\ +\n\ +#ifdef __GLSL_CG_DATA_TYPES\n\ +// NVidia specific\n\ +// These pragmas make a MASSIVE difference on some quadro cards.\n\ +// But no difference on my 6600.\n\ +#pragma optionNV(fastmath on)\n\ +#pragma optionNV(fastprecision on)\n\ +#pragma optionNV(ifcvt none)\n\ +#pragma optionNV(inline all)\n\ +#pragma optionNV(strict on)\n\ +#pragma optionNV(unroll all)\n\ +#endif\n\ +\n\ +\ +uniform sampler2D mmTexture;\n\ +uniform sampler2D mmLookup;\n\ +uniform vec4 mmTextureSize;\n\ +\ +in vec4 c5;\n\ +\ +out vec4 fragColour;\n\ +\ +vec4 RGB2YUV(in vec4 val)\n\ +{\n\ + return mat4(\ + 0.299, 0.587, 0.114, 0,\n\ + -0.14713, -0.28886, 0.436, 0,\n\ + 0.615, -0.51499, -0.10001, 0,\n\ + 0, 0, 0, 0\n\ + ) * val;\ +}\n\ +\ +bool Diff(in vec4 YUV1, in vec4 YUV2)\n\ +{\n\ + return any(\n\ + greaterThan(\n\ + abs(YUV1 - YUV2),\n\ + vec4(48.0/255.0, 7.0/255.0, 6.0/255.0, 0)\n\ + ).xyz\n\ + );\n\ +}\n\ +\ +void main()\n\ +{\n\ + vec4 c_offsetX = vec4(1.0/(mmTextureSize.x-1), 0, 0, 0);\n\ + vec4 c_offsetY = vec4(0, 1.0/(mmTextureSize.y-1), 0, 0);\n\ +\ + vec4 w[9];\n\ + w[0] = texture2DProj(mmTexture, c5 - c_offsetY - c_offsetX);\n\ + w[1] = texture2DProj(mmTexture, c5 - c_offsetY);\n\ + w[2] = texture2DProj(mmTexture, c5 - c_offsetY + c_offsetX);\n\ +\ + w[3] = texture2DProj(mmTexture, c5 - c_offsetX);\n\ + w[4] = texture2DProj(mmTexture, c5);\n\ + w[5] = texture2DProj(mmTexture, c5 + c_offsetX);\n\ +\ + w[6] = texture2DProj(mmTexture, c5 + c_offsetY - c_offsetX);\n\ + w[7] = texture2DProj(mmTexture, c5 + c_offsetY);\n\ + w[8] = texture2DProj(mmTexture, c5 + c_offsetY + c_offsetX);\n\ +\ + vec4 YUV[9];\n\ + YUV[0] = RGB2YUV(w[0]);\n\ + YUV[1] = RGB2YUV(w[1]);\n\ + YUV[2] = RGB2YUV(w[2]);\n\ + YUV[3] = RGB2YUV(w[3]);\n\ + YUV[4] = RGB2YUV(w[4]);\n\ + YUV[5] = RGB2YUV(w[5]);\n\ + YUV[6] = RGB2YUV(w[6]);\n\ + YUV[7] = RGB2YUV(w[7]);\n\ + YUV[8] = RGB2YUV(w[8]);\n\ +\ + int pattern = 0;\n\ + int flag = 1;\n\ + int k;\n\ + for (k = 0; k < 9; k++)\n\ + {\n\ + if (k == 4) continue;\n\ +\ + if (Diff(YUV[4], YUV[k]))\n\ + {\n\ + pattern = pattern + flag;\n\ + }\n\ + flag = flag * 2;\n\ + }\n\ +\ + // k is now diff.\n\ + k = 0;\n\ + if (Diff(YUV[1], YUV[5]))\n\ + {\n\ + k = k + 1;\n\ + }\n\ + if (Diff(YUV[5], YUV[7]))\n\ + {\n\ + k = k + 2;\n\ + }\n\ + if (Diff(YUV[7], YUV[3]))\n\ + {\n\ + k = k + 4;\n\ + }\n\ + if (Diff(YUV[3], YUV[1]))\n\ + {\n\ + k = k + 8;\n\ + }\n\ +\ + vec2 lookupMax = vec2(1.0/47.0, 1.0/4095);\n\ + ivec2 thisPixel = ivec2(fract(c5 * mmTextureSize) * 4.0);\n\ + ivec2 lookupIndex = ivec2((thisPixel.y*4+thisPixel.x)*3, (pattern * 16) + k);\n\ + vec4 weight1 = texture2D(mmLookup, lookupIndex * lookupMax);\n\ + vec4 weight2 = texture2D(mmLookup, (lookupIndex + ivec2(1, 0)) * lookupMax);\n\ + vec4 weight3 = texture2D(mmLookup, (lookupIndex + vec2(2, 0)) * lookupMax);\n\ +\ + // Dodgy fix for inaccurate lookups :-( Seemed to work on Quadro NVS 290 without this\n\ + float allW = dot(\n\ + vec4(1,1,1,0),\n\ + vec4(\n\ + dot(weight1, vec4(1,1,1,0)),\n\ + dot(weight2, vec4(1,1,1,0)),\n\ + dot(weight3, vec4(1,1,1,0)),\n\ + 0\n\ + )\n\ + );\n\ + fragColour =\n\ + w[0]*weight1.x + w[1]*weight1.y + w[2]*weight1.z +\n\ + w[3]*weight2.x + w[4]*weight2.y + w[5]*weight2.z +\n\ + w[6]*weight3.x + w[7]*weight3.y + w[8]*weight3.z;\n\ + fragColour = fragColour / allW;\n\ +\ +//if (allW > 1.100) { fragColour = vec4(0.0,0.0,1.0,0); } \n\ +//if (allW < 0.9) { fragColour = vec4(0.0,1.0,0.0,0); } \n\ +\ +}\n\ +"; + +} // namespace + +GLGraphics::GLGraphics( + Core& core, + GameboyGraphics& graphics, + bool fullscreen, + const double& scaleX, + const double& scaleY, + PixelScaler scaler, + ResampleMethod resampler + ) : + m_core(core), + m_graphics(graphics), + m_scaler(scaler), + m_resampler(resampler), + m_surface(0) +{ + initSurface(scaleX, scaleY, fullscreen); + initShader(); +} + +GLGraphics::~GLGraphics() +{ + if (m_surface) + { + SDL_FreeSurface(m_surface); + } +} + +void +GLGraphics::initSurface( + const double& xScale, const double& yScale, bool fullscreen) +{ + Info() << "Enabling OpenGL" << Log::endl; + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); //vsync + + unsigned int options(SDL_OPENGL); + if (fullscreen) + { + options |= SDL_FULLSCREEN; + } + + m_surface = + SDL_SetVideoMode(160*xScale, 144*yScale, 32, options); + + glEnable(GL_TEXTURE_2D); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0f, 160, 144, 0.0f, -1.0f, 1.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + SDL_WM_SetCaption("glBoy", "glBoy"); + // TODO SDL_WM_SetIcon +} + +void +GLGraphics::initShader() +{ + m_vertexShader = glCreateShader(GL_VERTEX_SHADER); + m_fragShader = glCreateShader(GL_FRAGMENT_SHADER); + + if (!m_vertexShader || !m_fragShader) openglError(); + + glShaderSource(m_vertexShader, 1, &s_VectorSource, NULL); + glShaderSource(m_fragShader, 1, &s_FragmentSource, NULL); + + glCompileShader(m_vertexShader); + if (glGetError() != GL_NO_ERROR) shaderError(m_vertexShader); + glCompileShader(m_fragShader); + if (glGetError() != GL_NO_ERROR) shaderError(m_fragShader); + + m_program = glCreateProgram(); + if (!m_program) openglError(); + + glAttachShader(m_program, m_vertexShader); + if (glGetError() != GL_NO_ERROR) programError(); + glAttachShader(m_program, m_fragShader); + if (glGetError() != GL_NO_ERROR) programError(); + glLinkProgram(m_program); + if (glGetError() != GL_NO_ERROR) programError(); + + glGenTextures(1, &m_lookupTexture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_lookupTexture); + + // Only nearest neighbour will work + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Create the texture image + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGBA8, + 48, + 4096, + 0, + GL_BGRA, // Must match struct Pixel + GL_UNSIGNED_BYTE, + m_hq4x.getHq4xLookupTable() + ); + + glUseProgram(m_program); + if (glGetError() != GL_NO_ERROR) programError(); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_lookupTexture); + GLint tex = glGetUniformLocation(m_program, "mmLookup"); + glUniform1i(tex, 1); // Bind Texture 1 +} + +void +GLGraphics::prepareShader(GLuint texture) +{ + glUseProgram(m_program); + if (glGetError() != GL_NO_ERROR) programError(); + + //glBindFragDataLocationEXT(m_program,0,"fragColor"); + + GLint size(glGetUniformLocation(m_program, "mmTextureSize")); + glUniform4f(size, 160, 144, 0, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + GLint tex(glGetUniformLocation(m_program, "mmTexture")); + glUniform1i(tex, 0); // Bind Texture 0 +} + +void +GLGraphics::render() +{ + if (SDL_MUSTLOCK(m_surface)) + { + if (SDL_LockSurface(m_surface) < 0) + { + return; + } + } + + uint8_t textureData[144*160]; + m_graphics.getGreyscaleFrame(&textureData[0]); + + // Allocate texture + GLuint texture; + glGenTextures(1, &texture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + + // Set the texture's stretching properties + // When using opengl shader scalers, we need to use nearest. + uint32_t resample( + (m_scaler == Scaler_None && m_resampler == Resample_Bilinear) ? + GL_LINEAR : GL_NEAREST + ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, resample); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, resample); + + // Create the texture image + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGBA, + 160, + 144, + 0, + GL_LUMINANCE, + GL_UNSIGNED_BYTE, + &textureData + ); + + prepareShader(texture); + + // Output texture. + + glBegin(GL_QUADS); + glTexCoord2i( 0, 0 ); glVertex2i( 0, 0 ); + glTexCoord2i( 1, 0 ); glVertex2i( 160, 0 ); + glTexCoord2i( 1, 1 ); glVertex2i( 160, 144 ); + glTexCoord2i( 0, 1 ); glVertex2i( 0, 144); + glEnd(); + + glLoadIdentity(); + SDL_GL_SwapBuffers(); + glDeleteTextures( 1, &texture ); + + if (SDL_MUSTLOCK(m_surface)) + { + SDL_UnlockSurface(m_surface); + } +} + +void +GLGraphics::openglError() +{ + Err() << + "Fatal OpenGL error in hq4x shader: " << + gluErrorString(glGetError()) << Log::endl; + abort(); +} + +void +GLGraphics::shaderError(GLint shader) +{ + char buffer[2048]; + int len; + glGetShaderInfoLog(shader, sizeof(buffer), &len, &buffer[0]); + + Err() << + "Fatal error using hq4x shader: " << + gluErrorString(glGetError()) << ": " << + buffer << Log::endl; + + abort(); +} + +void +GLGraphics::programError() +{ + char buffer[2048]; + int len; + glGetProgramInfoLog(m_program, sizeof(buffer), &len, &buffer[0]); + + Err() << + "Fatal error using hq4x program: " << + gluErrorString(glGetError()) << ": " << + buffer << Log::endl; + + abort(); +} diff --git a/sdl/GLGraphics.hh b/sdl/GLGraphics.hh new file mode 100644 index 0000000..b36ec90 --- /dev/null +++ b/sdl/GLGraphics.hh @@ -0,0 +1,85 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_GLGRAPHICS_HH +#define GLBOY_GLGRAPHICS_HH + +#include "glBoy.hh" +#include "Core.hh" +#include "GameboyGraphics.hh" + +#include "hq4x.hh" + +#include +#include + +#include + +#include + +namespace glBoy +{ + +class GLGraphics +{ +public: + enum PixelScaler { Scaler_None, Scaler_Scale2x, Scaler_hq4x }; + enum ResampleMethod { Resample_Nearest, Resample_Bilinear }; + + GLGraphics( + Core& core, + GameboyGraphics& graphics, + bool fullscreen, + const double& scaleX, + const double& scaleY, + PixelScaler scaler, + ResampleMethod resampler + ); + ~GLGraphics(); + + void render(); +private: + void initSurface( + const double& xScale, const double& yScale, bool fullscreen); + void initShader(); + + void openglError(); + void shaderError(GLint shader); + void programError(); + + void prepareShader(GLuint texture); + + Core& m_core; + GameboyGraphics& m_graphics; + + PixelScaler m_scaler; + ResampleMethod m_resampler; + + GLint m_vertexShader; + GLint m_fragShader; + GLint m_program; + GLuint m_lookupTexture; + Hq4x m_hq4x; + + // Output device + SDL_Surface* m_surface; +}; + +} // namespace glBoy + +#endif + diff --git a/Main.cc b/sdl/Main.cc similarity index 80% rename from Main.cc rename to sdl/Main.cc index bd7ae88..29727ac 100644 --- a/Main.cc +++ b/sdl/Main.cc @@ -15,7 +15,6 @@ // You should have received a copy of the GNU General Public License // along with glBoy. If not, see . -#define GLX_GLXEXT_PROTOTYPES 1 #include "glBoy.hh" #include "Core.hh" #include "GameboyCart.hh" @@ -25,15 +24,14 @@ #include "GameboyTimer.hh" #include "RAM.hh" #include "ROM.hh" +#include "sdl/GLGraphics.hh" +#include "sdl/SDLKeys.hh" +#include "sdl/SDLSound.hh" #include "Log.hh" -#include #include -#include -#include #include -#include #include #include @@ -47,6 +45,8 @@ #include #include +#include + namespace { enum Constants @@ -58,39 +58,6 @@ namespace GLBOY_MAX_HEIGHT = 1440, }; - SDL_Surface* - initOpenGL(double xScale, double yScale, bool fullscreen) - { - glBoy::Info() << "Enabling OpenGL" << glBoy::Log::endl; - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - unsigned int options(SDL_OPENGL); - if (fullscreen) - { - options |= SDL_FULLSCREEN; - } - - SDL_Surface* surface( - SDL_SetVideoMode(160*xScale, 144*yScale, 32, options) - ); - - // v-sync - //::glXSwapIntervalSGI(1); - - glEnable(GL_TEXTURE_2D); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0f, 160, 144, 0.0f, -1.0f, 1.0f); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - return surface; - } - void usage() { std::cout << std::endl << @@ -123,7 +90,7 @@ namespace if (entry && entry->pw_dir) { std::stringstream file; - file << entry->pw_dir << "/" << ".glBoy"; + file << entry->pw_dir << "/.glBoy/config"; try { config.readFile(file.str().c_str()); @@ -150,10 +117,20 @@ namespace } } } +} +extern "C" +{ + static void* start(void* val) + { + glBoy::Core* core = reinterpret_cast(val); + core->run(); + return NULL; + } } int main(int argc, char** argv) +{ { std::cout << PACKAGE_STRING << " <" << PACKAGE_BUGREPORT << ">" << std::endl; libconfig::Config config; @@ -175,8 +152,8 @@ int main(int argc, char** argv) std::string filename("tetris.gb"); config.lookupValue("glBoy.rom", filename); - glBoy::GameboyGraphics::PixelScaler scaler(glBoy::GameboyGraphics::Scaler_hq4x); - glBoy::GameboyGraphics::ResampleMethod resampler(glBoy::GameboyGraphics::Resample_Bilinear); + glBoy::GLGraphics::PixelScaler scaler(glBoy::GLGraphics::Scaler_hq4x); + glBoy::GLGraphics::ResampleMethod resampler(glBoy::GLGraphics::Resample_Bilinear); bool fullscreen(false); double scaleX(4.0); double scaleY(4.0); @@ -228,11 +205,11 @@ int main(int argc, char** argv) { if (std::string(optarg) == "bilinear") { - resampler = glBoy::GameboyGraphics::Resample_Bilinear; + resampler = glBoy::GLGraphics::Resample_Bilinear; } else { - resampler = glBoy::GameboyGraphics::Resample_Nearest; + resampler = glBoy::GLGraphics::Resample_Nearest; } }; break; case 's': @@ -250,15 +227,15 @@ int main(int argc, char** argv) { if (std::string(optarg) == "hq4x") { - scaler = glBoy::GameboyGraphics::Scaler_hq4x; + scaler = glBoy::GLGraphics::Scaler_hq4x; } else if (std::string(optarg) == "scale2x") { - scaler = glBoy::GameboyGraphics::Scaler_Scale2x; + scaler = glBoy::GLGraphics::Scaler_Scale2x; } else { - scaler = glBoy::GameboyGraphics::Scaler_None; + scaler = glBoy::GLGraphics::Scaler_None; } }; break; case 'w': @@ -294,6 +271,7 @@ int main(int argc, char** argv) glBoy::Info() << "Loading cartridge from file " << filename << glBoy::Log::endl; +//TODO catch GameboyCart::exception glBoy::GameboyCart cart(core, filename); std::stringstream cartInfo; cart.dump(cartInfo); @@ -309,13 +287,9 @@ int main(int argc, char** argv) SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO); - SDL_Surface* surface(initOpenGL(scaleX, scaleY, fullscreen)); - - glBoy::GameboyGraphics graphics(core, surface, scaler, resampler); - - SDL_WM_SetCaption("glBoy", "glBoy"); - // TODO SDL_WM_SetIcon - + glBoy::GameboyGraphics graphics(core); + glBoy::GLGraphics renderer( + core, graphics, fullscreen, scaleX, scaleY, scaler, resampler); // Map the standard internal RAM, plus shadow. glBoy::MemoryMap& map(core.getMemoryMap()); @@ -328,19 +302,12 @@ int main(int argc, char** argv) // Most interaction between GB hardware and the program occurs here. { std::shared_ptr mem(new glBoy::RAM(128)); - map.map(0xFF80, 128, mem); - } - - // Map the Interrupt flags. Currently handled in Core.cc - { - std::shared_ptr mem(new glBoy::RAM(1)); - mem->write8(0, 0); - map.map(0xFF0F, 1, mem); + map.map(0xFF80, 127, mem); } // Map other devices std::shared_ptr joypad( - new glBoy::GameboyJoypad(core, config) + new glBoy::GameboyJoypad(core) ); map.map(0xFF00, 1, joypad); std::shared_ptr timer( @@ -362,11 +329,37 @@ int main(int argc, char** argv) core.getRegisters().DE = glBoy::htoz(0x00D8); core.getRegisters().HL = glBoy::htoz(0x014D); - core.run(); + // Run the core in a new thread. + // OpenGL output must be in the main thread. + pthread_t thread( + pthread_create(&thread, NULL, start, &core)); + sleep(1); + + glBoy::SDLKeys keys(core, *joypad, config); + glBoy::SDLSound sdlSound(*sound); + + timespec loopTimer; + while (core.isRunning()) + { + clock_gettime(CLOCK_REALTIME, &loopTimer); + + keys.poll(); + renderer.render(); + + loopTimer.tv_nsec += 8000000; // Cap at 120fps + if (loopTimer.tv_nsec > 1e9) + { + loopTimer.tv_sec++; + loopTimer.tv_nsec -= 1e9; + } + + keys.poll(); + clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &loopTimer, NULL); + } std::cerr << "Done" << std::endl; - SDL_FreeSurface(surface); +} SDL_Quit(); } diff --git a/sdl/SDLKeys.cc b/sdl/SDLKeys.cc new file mode 100644 index 0000000..a258074 --- /dev/null +++ b/sdl/SDLKeys.cc @@ -0,0 +1,140 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" +#include "SDLKeys.hh" + +#include +#include +#include + +using namespace glBoy; + +namespace +{ + void setConfiguredKey( + const std::map& keyMap, + const libconfig::Config& config, + const std::string& configParam, + int& mappedKey + ) + { + if (config.exists(configParam)) + { + std::string value; + config.lookupValue(configParam, value); + std::map::const_iterator it( + keyMap.find(value) + ); + if (!value.empty() && it != keyMap.end()) + { + mappedKey = it->second; + } + } + } +} + +SDLKeys::SDLKeys( + Core& core, + GameboyJoypad& joypad, + const libconfig::Config& config + ) : + m_core(core), + m_joypad(joypad) +{ + std::map sdlKeyMap; + + for (int i = SDLK_FIRST; i < SDLK_LAST; ++i) + { + std::string keyName = SDL_GetKeyName(static_cast(i)); + if (keyName.find("SDLK_") == 0) + { + keyName = keyName.substr(5); + } + sdlKeyMap[keyName] = i; + } + + m_inputs[DOWN].sdlKey = SDLK_DOWN; + //m_inputs[DOWN].sdlJoystick = 0 or none or something; + m_inputs[UP].sdlKey = SDLK_UP; + m_inputs[LEFT].sdlKey = SDLK_LEFT; + m_inputs[RIGHT].sdlKey = SDLK_RIGHT; + m_inputs[START].sdlKey = SDLK_RETURN; + m_inputs[SELECT].sdlKey = SDLK_BACKSPACE; + m_inputs[A].sdlKey = SDLK_a; + m_inputs[B].sdlKey = SDLK_s; + + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.up", m_inputs[UP].sdlKey); + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.down", m_inputs[DOWN].sdlKey); + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.left", m_inputs[LEFT].sdlKey); + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.right", m_inputs[RIGHT].sdlKey); + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.start", m_inputs[START].sdlKey); + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.select", m_inputs[SELECT].sdlKey); + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.a", m_inputs[A].sdlKey); + setConfiguredKey(sdlKeyMap, config, "glBoy.keys.b", m_inputs[B].sdlKey); + + poll(); +} + +void +SDLKeys::poll() +{ + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + switch(event.type) + { + case SDL_KEYUP: + case SDL_KEYDOWN: + { + if (event.key.keysym.sym == SDLK_ESCAPE) + { + m_core.stop(); + } + + for (int i = 0; i < 8; ++i) + { + if (event.key.keysym.sym == m_inputs[i].sdlKey) + { + m_inputs[i].depressed = (event.key.type == SDL_KEYDOWN); + break; + } + } + }; break; + + case SDL_QUIT: + m_core.stop(); + break; + + default: + break; + } + } + + m_joypad.setInput( + m_inputs[UP].depressed, + m_inputs[DOWN].depressed, + m_inputs[LEFT].depressed, + m_inputs[RIGHT].depressed, + m_inputs[START].depressed, + m_inputs[SELECT].depressed, + m_inputs[A].depressed, + m_inputs[B].depressed + ); +} + diff --git a/sdl/SDLKeys.hh b/sdl/SDLKeys.hh new file mode 100644 index 0000000..ab6db46 --- /dev/null +++ b/sdl/SDLKeys.hh @@ -0,0 +1,59 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_SDLKEYS_HH +#define GLBOY_SDLKEYS_HH + +#include "glBoy.hh" +#include "Core.hh" +#include "GameboyJoypad.hh" + +#include +#include + +namespace glBoy +{ + +class SDLKeys +{ +public: + SDLKeys( + Core& core, + GameboyJoypad& joypad, + const libconfig::Config& config); + + void poll(); + +private: + Core& m_core; + GameboyJoypad& m_joypad; + + struct Input + { + Input() : sdlKey(0), depressed(false) {} + int sdlKey; + int sdlJoystick; + bool depressed; + }; + + enum Inputs { UP = 0, DOWN, LEFT, RIGHT, A, B, START, SELECT }; + Input m_inputs[8]; +}; + +} // namespace glBoy + +#endif diff --git a/sdl/SDLSound.cc b/sdl/SDLSound.cc new file mode 100644 index 0000000..11580b9 --- /dev/null +++ b/sdl/SDLSound.cc @@ -0,0 +1,53 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" +#include "SDLSound.hh" + +#include +#include +#include + +using namespace glBoy; + +extern "C" +{ + void mixaudio(void *userData, Uint8 *stream, int len) + { + GameboySound* ourObject(reinterpret_cast(userData)); + ourObject->mixSounds(reinterpret_cast(stream), len / 2); + } +} + +SDLSound::SDLSound(GameboySound& gameboySound) +{ + SDL_AudioSpec fmt; + + fmt.freq = GameboySound::SAMPLE_RATE; + fmt.format = AUDIO_S16SYS; + fmt.channels = 1; + fmt.samples = 512; + fmt.callback = mixaudio; + fmt.userdata = &gameboySound; + + if ( SDL_OpenAudio(&fmt, NULL) < 0 ) + { + assert(false); + } + SDL_PauseAudio(0); +} + diff --git a/sdl/SDLSound.hh b/sdl/SDLSound.hh new file mode 100644 index 0000000..0b3eae8 --- /dev/null +++ b/sdl/SDLSound.hh @@ -0,0 +1,40 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_SDLSOUND_HH +#define GLBOY_SDLSOUND_HH + +#include "glBoy.hh" +#include "GameboySound.hh" + +#include + +namespace glBoy +{ + +class SDLSound +{ +public: + SDLSound(GameboySound& gameboySound); + + void mixSounds(Uint8* stream, int len); +private: +}; + +} // namespace glBoy + +#endif