+++ /dev/null
-// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
-//
-// 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 <http://www.gnu.org/licenses/>.
-
-#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
-};
{\r
namespace ALU\r
{\r
-void load();\r
-extern const uint8_t ParityFlagLookup[256];\r
-\r
inline uint8_t\r
add(uint8_t a, uint8_t b, Flags& flags, bool carry = false)\r
{\r
uint8_t c = (carry && flags.C) ? 1 : 0;\r
uint8_t result(a + b + c);\r
\r
- flags.byte =\r
- (result & 0x80) | // S MSB\r
- ((result == 0) ? 0x40 : 0) | // Z\r
- // U5 TODO\r
- ((a ^ b ^ result) & 0x10) | // H\r
- // U3 TODO\r
-\r
- // If a and b have the same sign, and the result\r
- // has a different sign, then we overflowed the\r
- // register.\r
- // Can never overflow if a and b have different signs. (even with\r
- // carry, since 127 + -1 + carry == 127).\r
- // Step 1) Determine whether a and b have the same sign. (a NXOR b)\r
- // Step 2) Determine whether the result has // a different sign.\r
- ((((~(a ^ b)) & (result ^ a)) & 0x80) >> 5) |// P\r
-\r
- // N\r
- (((uint16_t(a) + uint16_t(b) + c) & 0x100) >> 8); // C\r
+ flags.C = ((uint16_t(a) + uint16_t(b) + c) & 0x100) ? 1 : 0;\r
+ flags.H = ((a ^ b ^ result) & 0x10) ? 1 : 0;\r
+ flags.N = 0;\r
+ flags.Z = (result == 0) ? 1 : 0;\r
\r
return result;\r
}\r
if (carry)\r
{\r
// Flags only affected for adc.\r
- flags.S = (result & 0x8000) ? 1 : 0;\r
flags.Z = (result == 0);\r
- flags.P = (((~(aHost ^ bHost)) & (result ^ aHost)) & 0x8000) ? 1 : 0;\r
-\r
}\r
\r
flags.N = 0;\r
return glBoy::htoz(result);\r
}\r
\r
+inline DWORD\r
+add(DWORD a, int8_t b, Flags& flags)\r
+{\r
+ const uint16_t aHost(a.host());\r
+\r
+ uint16_t result(aHost + b);\r
+\r
+ flags.C = (aHost & 0xFF) + uint8_t(b) > 0xFF ? 1 : 0;\r
+ flags.N = 0;\r
+ flags.H = ((aHost ^ b ^ result) & 0x10) ? 1 : 0;\r
+ flags.Z = 0;\r
+\r
+ return glBoy::htoz(result);\r
+}\r
+\r
inline DWORD\r
adc(DWORD a, DWORD b, Flags& flags)\r
{\r
uint8_t c = (borrow && flags.C) ? 1 : 0;\r
uint8_t result(a - b - c);\r
\r
- flags.byte =\r
- (result & 0x80) | // S MSB\r
- ((result == 0) ? 0x40 : 0) | // Z\r
- // U5 TODO\r
- (((a & 0xf) - (b & 0xf)) & 0x10) | // H\r
- // U3 TODO\r
-\r
- // Detected signed overflow\r
- ((int16_t(static_cast<int8_t>(a)) -\r
- int16_t(static_cast<int8_t>(b)) -\r
- c\r
- ) ==\r
- static_cast<int8_t>(result) ? 0 : 0x4) | // P\r
-\r
- 0x2 | // N\r
- (((uint16_t(a) - uint16_t(b) - c) & 0x100) >> 8); // C\r
+ flags.C = ((uint16_t(a) - uint16_t(b) - c) & 0x100) ? 1 : 0;\r
+ flags.H = (((a & 0xf) - (b & 0xf) - c) & 0x10) ? 1 : 0;\r
+ flags.N = 1;\r
+ flags.Z = (result == 0) ? 1 : 0;\r
\r
return result;\r
}\r
uint16_t c = (borrow && flags.C) ? 1 : 0;\r
uint16_t result(aHost - bHost - c);\r
\r
- flags.byte =\r
- ((result & 0x8000) >> 8) | // S MSB\r
- ((result == 0) ? 0x40 : 0) | // Z\r
- // U5 TODO\r
- ((((aHost & 0xf00) - (bHost & 0xf00)) & 0x1000) >> 8) | // H\r
- // U3 TODO\r
-\r
- // Detected signed overflow\r
- ((int32_t(static_cast<int16_t>(aHost)) -\r
- int32_t(static_cast<int16_t>(bHost)) -\r
- c\r
- ) ==\r
- static_cast<int16_t>(result) ? 0 : 0x4) | // P\r
-\r
- 0x2 | // N\r
- (((uint32_t(aHost) - uint32_t(bHost) - c) & 0x10000) >> 16); // C\r
+ flags.C = (((uint32_t(aHost) - uint32_t(bHost) - c) & 0x10000)) ? 1 : 0;\r
+ flags.H = (((aHost & 0xf00) - (bHost & 0xf00)) & 0x1000) ? 1 : 0;\r
+ flags.N = 1;\r
+ flags.Z = (result == 0) ? 1 : 0;\r
\r
return htoz(result);\r
}\r
bitwiseAnd(uint8_t a, uint8_t b, Flags& flags)\r
{\r
uint8_t result(a & b);\r
- flags.byte =\r
- (result & 0x80) | // S\r
- ((result == 0) ? 0x40 : 0) | // Z\r
- // U5\r
- 0x10 | // H\r
- // U3\r
- ParityFlagLookup[result]; // P\r
- // N\r
- // C\r
+\r
+ flags.C = 0;\r
+ flags.H = 1;\r
+ flags.N = 0;\r
+ flags.Z = (result == 0) ? 1 : 0;\r
+\r
return result;\r
}\r
\r
bitwiseOr(uint8_t a, uint8_t b, Flags& flags)\r
{\r
uint8_t result(a | b);\r
- flags.byte =\r
- (result & 0x80) | // S\r
- ((result == 0) ? 0x40 : 0) | // Z\r
- // U5\r
- // H\r
- // U3\r
- ParityFlagLookup[result]; // P\r
- // N\r
- // C\r
+\r
+ flags.C = 0;\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.Z = (result == 0) ? 1 : 0;\r
return result;\r
}\r
\r
bitwiseXor(uint8_t a, uint8_t b, Flags& flags)\r
{\r
uint8_t result(a ^ b);\r
- flags.byte =\r
- (result & 0x80) | // S\r
- ((result == 0) ? 0x40 : 0) | // Z\r
- // U5\r
- // H\r
- // U3\r
- ParityFlagLookup[result]; // P\r
- // N\r
- // C\r
+\r
+ flags.C = 0;\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.Z = (result == 0) ? 1 : 0;\r
return result;\r
}\r
\r
flags.H = 0;\r
flags.N = 0;\r
flags.C = msb;\r
-\r
if (setAllFlags)\r
{\r
- flags.byte |= (a & 0x80);\r
flags.Z = (a == 0) ? 1 : 0;\r
- flags.byte |= ParityFlagLookup[a];\r
}\r
-// TODO U3 U5\r
+ else\r
+ {\r
+ flags.Z = 0;\r
+ }\r
\r
return a;\r
}\r
uint8_t msb(a >> 7);\r
a <<= 1;\r
\r
- flags.byte =\r
- (a & 0x80) | // S\r
- ((a == 0) ? 0x40 : 0) | // Z\r
- // U5\r
- // H\r
- // U3\r
- ParityFlagLookup[a] | // P\r
- // N\r
- msb; // C\r
+ flags.C = msb;\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.Z = (a == 0) ? 1 : 0;\r
\r
return a;\r
}\r
\r
if (setAllFlags)\r
{\r
- flags.byte |= (a & 0x80);\r
flags.Z = (a == 0) ? 1 : 0;\r
- flags.byte |= ParityFlagLookup[a];\r
}\r
-// TODO U3 U5\r
+ else\r
+ {\r
+ flags.Z = 0;\r
+ }\r
\r
return a;\r
}\r
// Restore sign bit\r
if (arithmatic) a |= msb;\r
\r
- flags.byte =\r
- (a & 0x80) | // S\r
- ((a == 0) ? 0x40 : 0) | // Z\r
- // U5\r
- // H\r
- // U3\r
- ParityFlagLookup[a] | // P\r
- // N\r
- lsb; // C\r
+ flags.C = lsb;\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.Z = (a == 0) ? 1 : 0;\r
\r
return a;\r
}\r
\r
inline void\r
-RLD(uint8_t& a, uint8_t& m, Flags& f)\r
+RLD(uint8_t& a, uint8_t& m, Flags& flags)\r
{\r
// Whoever came up with RLD was on crack.\r
\r
a = (a & 0xF0) | (m >> 4);\r
m = newM;\r
\r
- f.byte =\r
- (a & 0x80) | // S\r
- ((a == 0) ? 0x40 : 0) | // Z\r
- // U5\r
- // H\r
- // U3\r
- ParityFlagLookup[a]; // P\r
- // N\r
- // C\r
+ flags.C = 0;\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.Z = (a == 0) ? 1 : 0;\r
}\r
\r
inline void\r
-RRD(uint8_t& a, uint8_t& m, Flags& f)\r
+RRD(uint8_t& a, uint8_t& m, Flags& flags)\r
{\r
// Whoever came up with RRD was on crack.\r
\r
a = (a & 0xF0) | (m & 0xf);\r
m = newM;\r
\r
- f.byte =\r
- (a & 0x80) | // S\r
- ((a == 0) ? 0x40 : 0) | // Z\r
- // U5\r
- // H\r
- // U3\r
- ParityFlagLookup[a]; // P\r
- // N\r
- // C\r
+ flags.C = 0;\r
+ flags.H = 0;\r
+ flags.N = 0;\r
+ flags.Z = (a == 0) ? 1 : 0;\r
}\r
\r
} // namespace ALU\r
+++ /dev/null
-// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
-//
-// 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 <http://www.gnu.org/licenses/>.
-
-
-#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 <SDL/SDL.h>
-#include <SDL/SDL_opengl.h>
-#include <SDL/SDL_video.h>
-
-#include <cassert>
-#include <set>
-#include <sstream>
-
-#include <getopt.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-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<glBoy::MemoryMap::Memory> 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<glBoy::MemoryMap::Memory> 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<glBoy::MemoryMap::Memory> mem(new glBoy::RAM(128));
- map.map(0xFF80, 128, mem);
- }
-
- // Map the Interrupt flags. Currently handled in Core.cc
- {
- std::shared_ptr<glBoy::MemoryMap::Memory> mem(new glBoy::RAM(1));
- mem->write8(0, 0);
- map.map(0xFF0F, 1, mem);
- }
-
- // Map other devices
- std::shared_ptr<glBoy::GameboyJoypad> joypad(new glBoy::GameboyJoypad(core));
- map.map(0xFF00, 1, joypad);
- std::shared_ptr<glBoy::GameboyTimer> 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<glBoy::GameboySound> 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();
-}
-
#include "Log.hh"\r
#include "util.hh"\r
\r
+#include "RAM.hh"\r
+\r
#include <cassert>\r
#include <cstddef>\r
#include <limits>\r
#include <sstream>\r
\r
-#include <string.h> // ffs\r
+#include <strings.h> // ffs\r
\r
using namespace glBoy;\r
\r
+namespace\r
+{\r
+ class MappedRegister : public MemoryMap::Memory\r
+ {\r
+ public:\r
+ MappedRegister(uint8_t* reg) : m_reg(reg) {}\r
+ virtual uint8_t read8(uint16_t) { return *m_reg; }\r
+ virtual void write8(uint16_t, uint8_t value) { *m_reg = value; }\r
+\r
+ private:\r
+ uint8_t* m_reg; // Points to member variable of Core class.\r
+ };\r
+}\r
+\r
Core::Core() :\r
- m_iff1(0),\r
- m_iff2(0),\r
- m_pendingInterrupts(0),\r
+ m_running(true),\r
+ m_ime(0),\r
+ m_ie(0),\r
+ m_if(0),\r
m_halted(false),\r
m_clock(0),\r
m_nextPeriodicCallback(std::numeric_limits<cycle_t>::max())\r
{\r
m_reg.reset();\r
- ALU::load();\r
+\r
+ std::shared_ptr<MemoryMap::Memory> ifMemory(\r
+ new MappedRegister(&m_if));\r
+ m_mem.map(0xFF0F, 1, ifMemory);\r
+\r
+ std::shared_ptr<MemoryMap::Memory> ieMemory(\r
+ new MappedRegister(&m_ie));\r
+ m_mem.map(0xFFFF, 1, ieMemory);\r
}\r
\r
void\r
-Core::raiseInterrupt(int interrupt)\r
+Core::raiseInterrupt(uint8_t interrupt)\r
{\r
// TODO this interrupt handling is GB80 specific\r
-\r
- switch (interrupt)\r
- {\r
- case 0x40: m_pendingInterrupts |= 0x1; break;\r
- case 0x48: m_pendingInterrupts |= 0x2; break;\r
- case 0x50: m_pendingInterrupts |= 0x4; break;\r
- case 0x58: m_pendingInterrupts |= 0x8; break;\r
- case 0x60: m_pendingInterrupts |= 0x10; break;\r
- }\r
+ m_if = (m_if | (uint8_t(1) << ((interrupt - 0x40) / 8))) & m_ie;\r
}\r
\r
void\r
// handler\r
// TODO z80 only. Unused for gb80 uint8_t doublePrefixOperand(0);\r
\r
- while (true)\r
+#ifdef GLBOY_DEBUG\r
+ bool traceCPU(Log::Instance().isDebugLevelOn(Log::DBG_TRACE_CPU));\r
+#endif\r
+ while (m_running)\r
{\r
cycle_t clock = 0;\r
\r
uint16_t prefix(0); // Used for debug logging only.\r
\r
// Check for interrupts.\r
- if (m_pendingInterrupts)\r
+ if (m_if)\r
{\r
// Only call ONE interrupt, even if multiple triggered.\r
- uint8_t priorityBit(1 << (ffs(m_pendingInterrupts) - 1));\r
-\r
- // We clear the IF of the interrupt we end up calling.\r
- // BUT if there are 2 simulataneous interrupts, it is left\r
- // with bits set, but never called.\r
- uint8_t iFlag(m_mem.read8(htoz(0xFF0F)));\r
- iFlag |= m_pendingInterrupts;\r
- m_pendingInterrupts = 0;\r
-\r
- uint8_t ie(m_mem.read8(htoz(0xFFFF)));\r
-\r
- if (m_iff1 && (ie & priorityBit))\r
+ if (m_ime)\r
{\r
- // Clear the IF of ONLY the interrupt we end up calling.\r
- iFlag ^= priorityBit;\r
+ // We clear the IF of the interrupt we end up calling.\r
+ // BUT if there are 2 simulataneous interrupts, it is left\r
+ // with bits set\r
\r
- m_iff1 = 0;\r
- m_iff2 = 0;\r
+ uint8_t priorityBit(ffs(m_if) - 1);\r
+ m_if ^= (uint8_t(1) << priorityBit);\r
+ m_ime = 0;\r
\r
m_reg.SP.dec(2);\r
m_mem.write16(m_reg.SP, m_reg.PC);\r
\r
- static const int addr[] = {0x40, 0x48, 0x50, 0x58, 0x60};\r
- m_reg.PC = htoz(addr[ffs(priorityBit) - 1]);\r
-\r
- m_halted = false;\r
+ m_reg.PC = htoz((priorityBit * 8) + 0x40);\r
}\r
- m_mem.write8(htoz(0xFF0F), iFlag);\r
+ m_halted = false; // Interrupts always recover from HALT\r
}\r
- else if (!m_halted)\r
+\r
+ if (!m_halted)\r
{\r
- uint16_t opcodePC = m_reg.PC.host(); // For debugging\r
uint8_t opcode = op8();\r
\r
+#ifdef GLBOY_DEBUG\r
+ DWORD opcodePC = m_reg.PC; // For debugging\r
+\r
+ std::stringstream ss;\r
+ std::ostream& dbg(traceCPU ? ss : Log::Instance().nullStream());\r
+\r
+ if (m_mem.getBankSize(m_reg.PC))\r
+ {\r
+ dbg << hex(m_mem.getBankIndex(opcodePC)) << ".";\r
+ }\r
+ dbg << opcodePC << " ";\r
+#endif\r
+\r
goto *(OpcodeTable[opcode]);\r
\r
#include "InstructionSet_Opcodes.cc"\r
BAD_OPCODE:\r
Err() <<\r
"Unknown Opcode: " << glBoy::hex(opcode) <<\r
- " (PC=" << opcodePC << ")" <<\r
+ " (PC=" << m_reg.PC << ")" <<\r
Log::endl;\r
assert(false);\r
\r
END_INSTRUCTIONS: ;\r
+#ifdef GLBOY_DEBUG\r
+ TraceCPU() << ss.str() << " A=" << hex(m_reg.A) << Log::endl;\r
+#endif\r
}\r
else\r
{\r
m_clock += clock;\r
\r
\r
- if (m_halted && (m_pendingInterrupts == 0))\r
+ if (m_halted && (m_if == 0))\r
{\r
m_clock = std::max(m_clock, m_nextPeriodicCallback);\r
}\r
}\r
}\r
}\r
+\r
}\r
\r
Core::CallbackId\r
Registers& getRegisters() { return m_reg; }\r
\r
void run();\r
+ void resume() { m_running = true; }\r
+ void stop() { m_running = false; }\r
+ bool isRunning() const { return m_running; }\r
\r
cycle_t getClockCount() const { return m_clock; }\r
\r
- bit getIFF2() const { return m_iff2; }\r
-\r
- void raiseInterrupt(int interrupt);\r
+ void raiseInterrupt(uint8_t interrupt);\r
void halt() { m_halted = true; }\r
\r
typedef int CallbackId;\r
uint8_t op8();\r
DWORD op16();\r
\r
- // The Z80 can switch between register sets.\r
+ bool m_running;\r
+\r
Registers m_reg;\r
- Registers m_regDash;\r
\r
- // Interrupt enablement\r
- bit m_iff1;\r
- bit m_iff2;\r
- uint8_t m_pendingInterrupts;\r
+ // Interrupt master enable\r
+ bit m_ime;\r
+\r
+ // Interrupt enable - enables/disables specific interrupts\r
+ uint8_t m_ie;\r
\r
- enum InterruptMode { IM_0, IM_1, IM_2 };\r
- InterruptMode m_interruptMode;\r
+ // Interrupt flag - pending interrupts\r
+ uint8_t m_if;\r
\r
bool m_halted;\r
\r
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;
}
return out;\r
}\r
\r
+inline std::string hex(const DWORD& val)\r
+{\r
+ return hex(val.host());\r
+}\r
+\r
// Create a dword from z80-ordered mem.\r
inline DWORD CreateDWORD(uint8_t* in)\r
{\r
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+
+#include "glBoy.hh"
+#include "FPS.hh"
+#include "Log.hh"
+
+#include <cmath>
+
+#include <sys/time.h>
+#include <time.h>
+
+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<double>(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;
+ }
+}
+
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_FPS_HH
+#define GLBOY_FPS_HH
+
+#include "glBoy.hh"
+#include <string>
+
+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
+
#include <cassert>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <unistd.h>
+
using namespace glBoy;
using namespace zipper;
using namespace std;
{
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
{
m_mbc = "ROM";
- size_t romSize(std::min(0x8000ul, rom.size()));
+ size_t romSize(std::min(size_t(0x8000ul), rom.size()));
shared_ptr<MemoryMap::Memory> mem(
new ROM(&rom[0], romSize)
);
{
m_mbc = "MBC1";
- size_t romSize(std::min(0x200000ul, rom.size()));
+ size_t romSize(std::min(size_t(0x200000ul), rom.size()));
shared_ptr<MemoryMap::Memory> mem(
new MBC1(this, &rom[0], &rom[0] + romSize)
);
{
m_mbc = "MBC2";
- size_t romSize(std::min(0x40000ul, rom.size()));
+ size_t romSize(std::min(size_t(0x40000ul), rom.size()));
shared_ptr<MemoryMap::Memory> mem(
new MBC2(this, &rom[0], &rom[0] + romSize)
);
{
m_mbc = "MBC3";
- size_t romSize(std::min(0x200000ul, rom.size()));
+ size_t romSize(std::min(size_t(0x200000ul), rom.size()));
shared_ptr<MemoryMap::Memory> mem(
new MBC3(this, &rom[0], &rom[0] + romSize)
);
tmp << (cartRam / 1024) << "KB";
tmp >> m_extRamSize;
}
+
+ loadExtRam();
}
void
{
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<std::shared_ptr<MemoryMap::Memory> >::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<std::shared_ptr<MemoryMap::Memory> >::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());
+ }
+
+}
#include "glBoy.hh"
#include "Core.hh"
+#include <exception>
#include <ostream>
+#include <stdexcept>
#include <vector>
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,
void dump(std::ostream& out);
void setRamBank(size_t bank);
+
+ void loadExtRam();
+ void saveExtRam();
+
private:
void init(const std::vector<uint8_t>& rom);
Core& m_core;
// You should have received a copy of the GNU General Public License
// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
-#define GL_GLEXT_PROTOTYPES
-
#include "glBoy.hh"
#include "GameboyGraphics.hh"
#include "Log.hh"
#include "MemoryMap.hh"
-#include <GL/gl.h>
-#include <GL/glext.h>
-
-#include <SDL/SDL_opengl.h>
-
#include <cassert>
#include <limits>
-#include <sys/time.h>
-
+#include <string.h>
using namespace glBoy;
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(
)
),
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,
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;
}
{
// 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)
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);
);
}
}
- updateOutputDevice();
+ pthread_mutex_lock(&m_screenShadowMutex);
+ ::memcpy(m_screenShadow, m_screen, sizeof(m_screenShadow));
+ pthread_mutex_unlock(&m_screenShadowMutex);
+ m_fps.frameComplete();
}
GameboyGraphics::Sprite
}
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<double>(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]);
}
#include "glBoy.hh"
#include "Core.hh"
-
+#include "FPS.hh"
#include "MemoryMap.hh"
-#include "hq4x.hh"
-
-#include <SDL/SDL.h>
-#include <SDL/SDL_opengl.h>
-
#include <vector>
+#include <pthread.h>
+
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();
enum RenderMode{ RENDER_BLIT, RENDER_SPRITE };
void renderScreen();
- void displayStats();
void renderTile(
uint8_t tile,
};
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.
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;
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
// TODO implement interrupt!
-namespace
+GameboyJoypad::GameboyJoypad(Core& core) :
+ m_core(core),
+ m_state(Joypad_Off),
+ m_buttonState(0xffff)
{
- void setConfiguredKey(
- const std::map<std::string, int>& keyMap,
- const libconfig::Config& config,
- const std::string& configParam,
- int& mappedKey
- )
- {
- if (config.exists(configParam))
- {
- std::string value;
- config.lookupValue(configParam, value);
- std::map<std::string, int>::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<std::string, int> 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<SDLKey>(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)
break;
case Joypad_Directional:
- result = m_directionalState;
+ result = buttonState >> 8;
break;
case Joypad_Button:
- result = m_buttonState;
+ result = buttonState & 0xff;
break;
};
return result;
}
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);
}
#include "MemoryMap.hh"
-#include <libconfig.h++>
-#include <SDL/SDL.h>
-
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
using namespace glBoy;
-enum { SAMPLE_RATE = 48000 };
-
-extern "C"
-{
- void mixaudio(void *userData, Uint8 *stream, int len)
- {
- GameboySound* ourObject(reinterpret_cast<GameboySound*>(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
}
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))
channel.squareStep = 0;
}
}
- return sample;
+ std::fill(pcmBuffer + i, pcmBuffer + samples, 0);
}
void
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);
}
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)
}
else if (volume == 3)
{
- nibble >>= 3;
+ nibble >>= 2;
}
// Ok, nibble is now between 0 and 0xf. Map this to full range.
- sample = (static_cast<int16_t>(nibble) - 8) * 4096;
+ pcmBuffer[i] += (static_cast<int32_t>(nibble) - 8) * 4096;
++m_channel3.waveSamples;
if (m_channel3.waveSamples > m_channel3.samplesPerNibble * (m_channel3.wavePos + 1))
}
}
- return sample;
+ std::fill(pcmBuffer + i, pcmBuffer + samples, 0);
}
+
#include "MemoryMap.hh"
-#include <SDL/SDL.h>
-
namespace glBoy
{
class GameboySound : public MemoryMap::Memory
{
public:
+ enum Constants { SAMPLE_RATE = 48000 };
+
GameboySound(Core& core);
void periodicUpdate(Core::CallbackId id);
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)
int remainingLength;
};
- int16_t sample(SquareWave& channel);
+ void mixSamples(SquareWave& channel, int32_t* pcmBuffer, size_t samples);
void prepChannel(SquareWave& channel);
SquareWave m_channel1;
};
Noise m_channel4;
- int16_t sampleChannel4();
+ void mixSamplesChannel4(int32_t* pcmBuffer, size_t samples);
void prepChannel4();
struct Wave
};
Wave m_channel3;
- int16_t sampleChannel3();
+ void mixSamplesChannel3(int32_t* pcmBuffer, size_t samples);
void prepChannel3();
uint8_t m_channelControl;
#include <algorithm>
#include <cassert>
+#include <string.h>
#include <time.h>
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<void(void)>& setFrameSkip,
) :
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)
);
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<class T>
+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
{
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;
}
}
}
- 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
#include <time.h>
-#include <SDL/SDL.h>
-
namespace glBoy
{
int m_clockSleepDelay;
int m_clockSleepCounter;
+ long m_frameSkipAllowanceNs;
+
+ unsigned int m_tick;
};
} // namespace glBoy
<reg name="BC" mask="00" />\r
<reg name="DE" mask="01" />\r
<reg name="HL" mask="10" />\r
- <reg name="AF" mask="11" />\r
</registerMask>\r
\r
<registerMask name="p" maskBits="2" regBytes="2" variant="z80">\r
m_mem.write16(m_reg.SP, q);\r
</instruction>\r
\r
+<instruction name="push AF" clock="11" variant="gb80">\r
+<opcode mask="11110101"/>\r
+ m_reg.SP.dec(2);\r
+ m_reg.F.byte &= 0xF0;\r
+ m_mem.write16(m_reg.SP, m_reg.AF);\r
+</instruction>\r
+\r
+<instruction name="push AF" clock="11" variant="z80">\r
+<opcode mask="11110101"/>\r
+ m_reg.SP.dec(2);\r
+ m_mem.write16(m_reg.SP, m_reg.AF);\r
+</instruction>\r
+\r
<instruction name="push IX" clock="15">\r
<opcode prefix="0xDD" mask="11100101" />\r
m_reg.SP.dec(2);\r
m_reg.SP.inc(2);\r
</instruction>\r
\r
+<instruction name="pop AF" clock="10" variant="gb80">\r
+<opcode mask="11110001" />\r
+ m_reg.AF = m_mem.read16(m_reg.SP);\r
+ m_reg.F.byte &= 0xF0;\r
+ m_reg.SP.inc(2);\r
+</instruction>\r
+\r
+<instruction name="pop AF" clock="10" variant="z80">\r
+<opcode mask="11110001" />\r
+ m_reg.AF = m_mem.read16(m_reg.SP);\r
+ m_reg.SP.inc(2);\r
+</instruction>\r
+\r
<instruction name="pop IX" clock="14">\r
<opcode prefix="0xDD" mask="11100001" />\r
m_reg.IX = m_mem.read16(m_reg.SP);\r
\r
<instruction name="INC r" clock="4">\r
<opcode mask="00r100" />\r
+ bit oldC(m_reg.F.C);\r
r = ALU::add(r, 1, m_reg.F);\r
+ m_reg.F.C = oldC;\r
</instruction>\r
\r
<instruction name="INC (HL)" clock="11">\r
<opcode mask="00110100" />\r
+ bit oldC(m_reg.F.C);\r
m_mem.write8(m_reg.HL, ALU::add(m_mem.read8(m_reg.HL), 1, m_reg.F));\r
+ m_reg.F.C = oldC;\r
</instruction>\r
\r
<instruction name="INC (IX+d)" clock="23">\r
\r
<instruction name="DEC r" clock="4">\r
<opcode mask="00r101" />\r
+ bit oldC(m_reg.F.C);\r
r = ALU::sub(r, 1, m_reg.F);\r
+ m_reg.F.C = oldC;\r
</instruction>\r
\r
<instruction name="DEC (HL)" clock="11">\r
<opcode mask="00110101" />\r
+ bit oldC(m_reg.F.C);\r
m_mem.write8(m_reg.HL, ALU::sub(m_mem.read8(m_reg.HL), 1, m_reg.F));\r
+ m_reg.F.C = oldC;\r
</instruction>\r
\r
<instruction name="DEC (IX+d)" clock="23">\r
<instruction name="CCF" clock="4">\r
<opcode mask="00111111"/>\r
m_reg.F.C = (m_reg.F.C) ? 0 : 1;\r
+ m_reg.F.H = 0;\r
m_reg.F.N = 0;\r
</instruction>\r
\r
\r
<instruction name="DI" clock="4">\r
<opcode mask="11110011"/>\r
- m_iff1 = 0;\r
- m_iff2 = 0;\r
+ m_ime = 0;\r
</instruction>\r
\r
<instruction name="EI" clock="4">\r
<opcode mask="11111011"/>\r
- m_iff1 = 1;\r
- m_iff2 = 1;\r
+ m_ime = 1;\r
</instruction>\r
\r
<instruction name="IM 0" clock="8">\r
<opcode prefix="0xED" mask="01001101"/>\r
m_reg.PC = m_mem.read16(m_reg.SP);\r
m_reg.SP.inc(2);\r
- m_iff1 = 1;\r
- m_iff2 = 1;\r
+ m_ime = 1;\r
</instruction>\r
\r
<instruction name="RETN" clock="14">\r
// TODO interruptComplete();\r
</instruction>\r
\r
-<instruction name="RST p" clock="11">\r
+<instruction name="RST b" clock="11">\r
<opcode mask="11b111"/>\r
uint8_t addr;\r
switch (b)\r
<opcode mask="11011001" />\r
m_reg.PC = m_mem.read16(m_reg.SP);\r
m_reg.SP.inc(2);\r
- m_iff1 = 1;\r
- m_iff2 = 1;\r
+ m_ime = 1;\r
</instruction>\r
\r
<instruction name="STOP" clock="4" variant="gb80">\r
\r
<instruction name="ADD SP,d" clock="4" variant="gb80">\r
<opcode mask="11101000" signed_operand="d"/>\r
- m_reg.SP = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F);\r
+ m_reg.SP = ALU::add(m_reg.SP, d, m_reg.F);\r
</instruction>\r
\r
<instruction name="LD (N),A" clock="4" variant="gb80">\r
</instruction>\r
\r
\r
-<instruction name="LDHL SP,d" clock="4" variant="gb80">\r
+<instruction name="LDHL SP,d" clock="12" variant="gb80">\r
<opcode mask="11111000" signed_operand="d"/>\r
- m_reg.HL = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F);\r
+ m_reg.HL = ALU::add(m_reg.SP, d, m_reg.F);\r
</instruction>\r
\r
<instruction name="LD A,(N)" clock="4" variant="gb80">\r
m_reg.F.C = 0;\r
</instruction>\r
\r
+\r
<!-- Zero-page gameboy instructions -->\r
-<instruction name="LD (FF00+n),A" clock="4" variant="gb80">\r
+<instruction name="LDH (FF00+n),A" clock="12" variant="gb80">\r
<opcode mask="11100000" operand="n"/>\r
m_mem.write8(htoz(0xFF00 + n), m_reg.A);\r
</instruction>\r
\r
-<instruction name="LD (FF00+C),A" clock="4" variant="gb80">\r
+<instruction name="LD (FF00+C),A" clock="8" variant="gb80">\r
<opcode mask="11100010"/>\r
m_mem.write8(htoz(0xFF00 + m_reg.C), m_reg.A);\r
</instruction>\r
\r
+<instruction name="LD A,(FF00+C)" clock="8" variant="gb80">\r
+<opcode mask="11110010"/>\r
+ m_reg.A = m_mem.read8(htoz(0xFF00 + m_reg.C));\r
+</instruction>\r
\r
-<instruction name="LD A,(FF00+d)" clock="4" variant="gb80">\r
+<instruction name="LDH A,(FF00+d)" clock="12" variant="gb80">\r
<opcode mask="11110000" operand="n"/>\r
m_reg.A = m_mem.read8(htoz(0xFF00 + n));\r
</instruction>\r
<opcode mask="11101100"/>\r
</instruction>\r
\r
-<instruction name="NOP_F2" clock="4" variant="gb80">\r
-<opcode mask="11110010"/>\r
-</instruction>\r
\r
<instruction name="NOP_F4" clock="4" variant="gb80">\r
<opcode mask="11110100"/>\r
return GetLog(Log::DBG_TRACE_MEM);
}
+Log& glBoy::TraceCPU()
+{
+ return GetLog(Log::DBG_TRACE_CPU);
+}
Log&
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)
Log& Info();
Log& Stat();
Log& TraceMem();
+ Log& TraceCPU();
class Log
{
DBG_INFO = 4,
DBG_STAT = 8,
DBG_TRACE_MEM = 16,
+ DBG_TRACE_CPU = 32,
DBG_NONE = 0xFFFFFFFF
};
void setDebugLevel(uint32_t levelMask);
bool isDebugLevelOn(DebugLevel level) const;
+ std::ostream& nullStream() { return m_nullStream; }
private:
friend Log& GetLog(int);
DebugLevel m_currentLevel;
std::stringstream m_collector;
+
+ class NullBuf : public std::streambuf
+ {
+ public:
+ };
+
+ NullBuf m_nullBuf;
+ std::ostream m_nullStream;
};
} // namespace glBoy
#include "glBoy.hh"
#include "MBC1.hh"
+#include "GameboyCart.hh"
#include "Log.hh"
-#include <cassert>
+#include <sstream>
using namespace glBoy;
using namespace std;
) :
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;
}
}
else
{
TraceMem() << "Attempt to read unmapped MBC1 rom data." << Log::endl;
- abort();
+ return 0;
}
}
{
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)
{
m_romBank |= (m_romHigh << 5);
}
-std::cerr << "MBC1 ROM LOW " << (int)m_romBank << std::endl;
}
else if (address < 0x6000)
{
else
{
m_romBank = m_romLow | (m_romHigh << 5);
-std::cerr << "MBC1 ROM HIGH " << (int)m_romBank << std::endl;
}
}
else
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;
}
}
}
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);
GameboyCart* m_parent;
std::vector<uint16_t> m_rom;
+ size_t m_bankSize;
enum Mode { ROM_SELECT, RAM_SELECT };
Mode m_mode;
uint8_t m_romHigh;
uint32_t m_romBank;
+
+ bool m_ramEnabled;
};
}
#include "Log.hh"
#include <cassert>
+#include <sstream>
using namespace glBoy;
using namespace std;
) :
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;
}
}
else
{
TraceMem() << "Attempt to read unmapped MBC2 rom data." << Log::endl;
- abort();
+ return 0;
}
}
{
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)
{
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);
GameboyCart* m_parent;
std::vector<uint16_t> m_rom;
+ size_t m_bankSize;
uint32_t m_romBank;
+
+ bool m_ramEnabled;
};
}
#include <cassert>
+#include <sstream>
+
using namespace glBoy;
using namespace std;
) :
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)
{
else
{
TraceMem() << "Attempt to read unmapped MBC3 rom data." << Log::endl;
- abort();
+ return 0;
}
}
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)
{
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);
GameboyCart* m_parent;
std::vector<uint16_t> m_rom;
+ size_t m_bankSize;
uint32_t m_romBank;
+
+ bool m_ramEnabled;
};
}
SUBDIRS = libzipper
-check_PROGRAMS = \
- test_alu
-
-TESTS = $(check_PROGRAMS)
-
-test_alu_SOURCES = Test.cc ALU.cc
-
dist_noinst_SCRIPTS = autogen.sh
EXTRA_DIST = \
glBoy_SOURCES = \
ALU.hh \
- ALU.cc \
Core.hh \
Core.cc \
DAA.hh \
DWORD.hh \
+ FPS.hh \
+ FPS.cc \
GameboyCart \
GameboyCart.cc \
GameboyGraphics.hh \
hq4x.cc \
Log.hh \
Log.cc \
- Main.cc \
MemoryMap.hh \
MemoryMap.cc \
MBC1.hh \
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
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)
-
\r
TraceMem() <<\r
"Registering " << glBoy::hex(base) <<\r
- " for " << glBoy::hex(size) << " bytes."<< Log::endl;\r
+ " for " << glBoy::hex(size) << " bytes." << Log::endl;\r
+}\r
+\r
+uint16_t\r
+MemoryMap::getBankIndex(DWORD address) const\r
+{\r
+ uint16_t offsetAddress(address.host());\r
+ const MappedArea& mapping(get(offsetAddress));\r
+ return mapping.memory->getBankIndex(offsetAddress - mapping.base);\r
+}\r
+\r
+uint16_t\r
+MemoryMap::getBankSize(DWORD address) const\r
+{\r
+ uint16_t offsetAddress(address.host());\r
+ const MappedArea& mapping(get(offsetAddress));\r
+ return mapping.memory->getBankSize(offsetAddress - mapping.base);\r
}\r
\r
void\r
{\r
const MappedArea& srcMap(get(srcAddress));\r
\r
- assert((srcAddress + bytes) <= (srcMap.base + srcMap.size));\r
-\r
- srcMap.memory->copy(dest, srcAddress - srcMap.base, bytes);\r
+ uint16_t offset(srcAddress - srcMap.base);\r
+ uint16_t copyBytes(std::min(srcMap.size - offset, bytes));\r
+ srcMap.memory->copy(dest, offset, copyBytes);\r
}\r
\r
+// Currently only used for loading saved games.\r
+void\r
+MemoryMap::copy(uint16_t destAddress, uint8_t* src, int bytes)\r
+{\r
+ const MappedArea& destMap(get(destAddress));\r
+\r
+ uint16_t offset(destAddress - destMap.base);\r
+\r
+ for (int i = 0; i < bytes && i < (destMap.size - offset); ++i)\r
+ {\r
+ destMap.memory->write8(offset + i, src[i]);\r
+ }\r
+}\r
void\r
MemoryMap::Memory::copy(uint8_t* dest, uint16_t address, int bytes)\r
{\r
virtual uint8_t read8(uint16_t address) = 0;\r
virtual void write8(uint16_t address, uint8_t value) = 0;\r
virtual void copy(uint8_t* dest, uint16_t address, int bytes);\r
+\r
+ virtual uint16_t getBankIndex(uint16_t) const { return 0; }\r
+ virtual uint16_t getBankSize(uint16_t) const { return 0; }\r
};\r
\r
MemoryMap();\r
\r
void remove(uint16_t base);\r
\r
+ uint16_t getBankIndex(DWORD address) const;\r
+ uint16_t getBankSize(DWORD address) const;\r
+\r
uint8_t read8(DWORD address, int8_t offset = 0);\r
void write8(DWORD address, uint8_t value);\r
void write8(DWORD address, int8_t offset, uint8_t value);\r
void write16(DWORD address, int8_t offset, DWORD value);\r
\r
void copy(uint8_t* dest, uint16_t srcAddress, int bytes);\r
+ void copy(uint16_t destAddress, uint8_t* src, int bytes);\r
\r
private:\r
struct MappedArea\r
{\r
MappedArea(\r
- uint16_t _base, uint16_t _size, std::shared_ptr<Memory> _mem\r
+ uint16_t _base,\r
+ uint16_t _size,\r
+ std::shared_ptr<Memory> _mem\r
) :\r
- base(_base), size(_size), memory(_mem) {}\r
+ base(_base),\r
+ size(_size),\r
+ memory(_mem)\r
+ {}\r
\r
uint16_t base;\r
uint16_t size;\r
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
uint8_t\r
RAM::read8(uint16_t address)\r
{\r
- return m_mem[address];\r
+ if (address < m_mem.size())\r
+ {\r
+ return m_mem[address];\r
+ }\r
+ else\r
+ {\r
+ return 0;\r
+ }\r
}\r
\r
void\r
RAM::write8(uint16_t address, uint8_t value)\r
{\r
- m_mem[address] = value;\r
+ if (address < m_mem.size())\r
+ {\r
+ m_mem[address] = value;\r
+ }\r
}\r
\r
\r
void\r
RAM::copy(uint8_t* dest, uint16_t address, int bytes)\r
{\r
- memcpy(dest, &m_mem[0] + address, bytes);\r
+ memcpy(\r
+ dest,\r
+ &m_mem[0] + address,\r
+ std::min(bytes, int(m_mem.size() - address)));\r
}\r
\r
virtual void write8(uint16_t address, uint8_t value);\r
virtual void copy(uint8_t* dest, uint16_t address, int bytes);\r
\r
-\r
private:\r
bool m_fourBitMode;\r
std::vector<uint8_t> m_mem;\r
uint8_t\r
RAM_4bit::read8(uint16_t address)\r
{\r
- return m_mem[address] & 0xF;\r
+ if (address < m_mem.size())\r
+ {\r
+ return m_mem[address] & 0xF;\r
+ }\r
+ else\r
+ {\r
+ return 0;\r
+ }\r
}\r
\r
void\r
RAM_4bit::write8(uint16_t address, uint8_t value)\r
{\r
- m_mem[address] = value & 0xF;\r
+ if (address < m_mem.size())\r
+ {\r
+ m_mem[address] = value & 0xF;\r
+ }\r
}\r
\r
\r
void\r
RAM_4bit::copy(uint8_t* dest, uint16_t address, int bytes)\r
{\r
- memcpy(dest, &m_mem[0] + address, bytes);\r
+ memcpy(\r
+ dest,\r
+ &m_mem[0] + address,\r
+ std::min(bytes, int(m_mem.size() - address)));\r
}\r
\r
using namespace glBoy;\r
\r
void\r
-Flags::set(op8_t operand, const Core& context)\r
+Flags::set(op8_t operand, const Core& /*context*/)\r
{\r
- S = static_cast<int8_t>(operand) < 0 ? 1 : 0;\r
+ //S = static_cast<int8_t>(operand) < 0 ? 1 : 0;\r
Z = operand == 0 ? 1 : 0;\r
- U5 = (operand & 0b00010000) >> 4;\r
+ //U5 = (operand & 0b00010000) >> 4;\r
H = 0;\r
- U3 = (operand & 0b00000100) >> 2;\r
- P = context.getIFF2();\r
+ //U3 = (operand & 0b00000100) >> 2;\r
+ //P = context.getIFF2();\r
N = 0;\r
// C is not affected\r
}\r
\r
void\r
-Flags::setCounter(DWORD BC)\r
+Flags::setCounter(DWORD /*BC*/)\r
{\r
// S is not affected\r
// Z is not affected\r
- U5 = (BC.host() & 0b00010000) >> 4;\r
+ //U5 = (BC.host() & 0b00010000) >> 4;\r
H = 0;\r
- U3 = (BC.host() & 0b00000100) >> 2;\r
- P = BC.host() ? 1 : 0;\r
+ //U3 = (BC.host() & 0b00000100) >> 2;\r
+ //P = BC.host() ? 1 : 0;\r
N = 0;\r
// C is not affected\r
}\r
\r
void\r
-Flags::setSub(op8_t A, op8_t B, DWORD BC)\r
+Flags::setSub(op8_t A, op8_t B, DWORD /*BC*/)\r
{\r
- S = (A > B) ? 1 : 0;\r
+ //S = (A > B) ? 1 : 0;\r
Z = (A == B) ? 1 : 0;\r
- U5 = (A & 0b00010000) >> 4;\r
+ //U5 = (A & 0b00010000) >> 4;\r
H = (B & 0xF) > (A & 0xF);\r
- U3 = (A & 0b00000100) >> 2;\r
- P = BC.host() ? 1 : 0;\r
+ //U3 = (A & 0b00000100) >> 2;\r
+ //P = BC.host() ? 1 : 0;\r
N = 1;\r
// C is not affected\r
}\r
{\r
struct __attribute__ ((__packed__))\r
{\r
- unsigned C:1; /// Carry. LSB of Flag register\r
- unsigned N:1; /// Add/Subtract\r
- unsigned P:1; /// (V) Parity/Overflow\r
- unsigned U3:1; /// 3rd bit of last 8bit op that altered flags\r
+ unsigned U1:1; // LSB\r
+ unsigned U2:1;\r
+ unsigned U3:1;\r
+ unsigned U4:1;\r
+ unsigned C:1; /// Carry.\r
unsigned H:1; /// Half-Carry (BCD)\r
- unsigned U5:1; /// 5th bit of last 8bit op that altered flags\r
+ unsigned N:1; /// Add/Subtract\r
unsigned Z:1; /// Zero Flag\r
- unsigned S:1; /// Sign Flag. MSB of Flag register\r
};\r
reg8_t byte;\r
};\r
h; \\r
};\r
\r
- // Note: The order of these unions/structs is important, as\r
- // offsets are taken into the Register struct\r
- reg8_t begin8[0];\r
- DWORD begin16[0];\r
-\r
union\r
{\r
Z80_REG_STRUCT(reg8_t B, reg8_t C);\r
+++ /dev/null
-// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
-//
-// 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 <http://www.gnu.org/licenses/>.
-
-#include "ALU.hh"
-#include "DAA.hh"
-
-#include "stddef.h"
-
-#include <cassert>
-#include <iostream>
-#include <ios>
-
-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;
-}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.codesrc.glboy"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <uses-sdk android:minSdkVersion="10" />
+ <uses-feature android:required="true" android:glEsVersion="0x00020000"></uses-feature>
+
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".GlBoyActivity"
+ android:label="@string/app_name"
+ android:configChanges="keyboardHidden|orientation">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
\ No newline at end of file
--- /dev/null
+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)
+
--- /dev/null
+#include <jni.h>
+#include <string.h>
+#include <android/log.h>
+
+#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<Core*>(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<MemoryMap::Memory> 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<MemoryMap::Memory> mem(new RAM(127));
+ map.map(0xFF80, 127, mem);
+ }
+
+ // Map other devices
+ std::shared_ptr<GameboyJoypad> joypad(
+ new GameboyJoypad(core)
+ );
+ map.map(0xFF00, 1, joypad);
+ std::shared_ptr<GameboyTimer> 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<GameboySound> 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<uint8_t*>(
+ j_env->GetPrimitiveArrayCritical(frameBuffer, 0));
+ GlobalState.m_graphics->getGreyscaleFrame(rawFrame); // thread safe
+ j_env->ReleasePrimitiveArrayCritical(frameBuffer, rawFrame, 0);
+ }
+}
+
+
+} // extern "C"
+
+
+
--- /dev/null
+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
+
--- /dev/null
+/* 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 <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> 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 <time.h>
+extern "C"
+{
+extern int clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec*);
+}
+
--- /dev/null
+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";
+}
# 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:
{
// - Applied clause 3 of LGPL 2.1 to alter license to GNU GPL 3. Updated\r
// License notice at top of file as required by clause 3.\r
\r
-#define GL_GLEXT_PROTOTYPES\r
-\r
#include "hq4x.hh"\r
\r
-#include <GL/glu.h>\r
-#include <GL/glext.h>\r
-\r
#include <cassert>\r
#include <cstdint>\r
#include <cmath>\r
\r
using namespace glBoy;\r
\r
-namespace\r
-{\r
-\r
-const char*\r
-s_VectorSource =\r
-"\\r
-//#version 130\n\\r
-\\r
-uniform vec4 mmTextureSize;\n\\r
-\\r
-varying vec4 c5;\n\\r
-\\r
-void main()\n\\r
-{\n\\r
- c5 = gl_MultiTexCoord0;\n\\r
-\\r
- gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n\\r
-}\n\\r
-";\r
-\r
-const char*\r
-s_FragmentSource =\r
-"\\r
-// Enable bitwise operators\n\\r
-#version 130\n\\r
-\n\\r
-#ifdef __GLSL_CG_DATA_TYPES\n\\r
-// NVidia specific\n\\r
-// These pragmas make a MASSIVE difference on some quadro cards.\n\\r
-// But no difference on my 6600.\n\\r
-#pragma optionNV(fastmath on)\n\\r
-#pragma optionNV(fastprecision on)\n\\r
-#pragma optionNV(ifcvt none)\n\\r
-#pragma optionNV(inline all)\n\\r
-#pragma optionNV(strict on)\n\\r
-#pragma optionNV(unroll all)\n\\r
-#endif\n\\r
-\n\\r
-\\r
-uniform sampler2D mmTexture;\n\\r
-uniform sampler2D mmLookup;\n\\r
-uniform vec4 mmTextureSize;\n\\r
-\\r
-in vec4 c5;\n\\r
-\\r
-out vec4 fragColour;\n\\r
-\\r
-vec4 RGB2YUV(in vec4 val)\n\\r
-{\n\\r
- return mat4(\\r
- 0.299, 0.587, 0.114, 0,\n\\r
- -0.14713, -0.28886, 0.436, 0,\n\\r
- 0.615, -0.51499, -0.10001, 0,\n\\r
- 0, 0, 0, 0\n\\r
- ) * val;\\r
-}\n\\r
-\\r
-bool Diff(in vec4 YUV1, in vec4 YUV2)\n\\r
-{\n\\r
- return any(\n\\r
- greaterThan(\n\\r
- abs(YUV1 - YUV2),\n\\r
- vec4(48.0/255.0, 7.0/255.0, 6.0/255.0, 0)\n\\r
- ).xyz\n\\r
- );\n\\r
-}\n\\r
-\\r
-void main()\n\\r
-{\n\\r
- vec4 c_offsetX = vec4(1.0/(mmTextureSize.x-1), 0, 0, 0);\n\\r
- vec4 c_offsetY = vec4(0, 1.0/(mmTextureSize.y-1), 0, 0);\n\\r
-\\r
- vec4 w[9];\n\\r
- w[0] = texture2DProj(mmTexture, c5 - c_offsetY - c_offsetX);\n\\r
- w[1] = texture2DProj(mmTexture, c5 - c_offsetY);\n\\r
- w[2] = texture2DProj(mmTexture, c5 - c_offsetY + c_offsetX);\n\\r
-\\r
- w[3] = texture2DProj(mmTexture, c5 - c_offsetX);\n\\r
- w[4] = texture2DProj(mmTexture, c5);\n\\r
- w[5] = texture2DProj(mmTexture, c5 + c_offsetX);\n\\r
-\\r
- w[6] = texture2DProj(mmTexture, c5 + c_offsetY - c_offsetX);\n\\r
- w[7] = texture2DProj(mmTexture, c5 + c_offsetY);\n\\r
- w[8] = texture2DProj(mmTexture, c5 + c_offsetY + c_offsetX);\n\\r
-\\r
- vec4 YUV[9];\n\\r
- YUV[0] = RGB2YUV(w[0]);\n\\r
- YUV[1] = RGB2YUV(w[1]);\n\\r
- YUV[2] = RGB2YUV(w[2]);\n\\r
- YUV[3] = RGB2YUV(w[3]);\n\\r
- YUV[4] = RGB2YUV(w[4]);\n\\r
- YUV[5] = RGB2YUV(w[5]);\n\\r
- YUV[6] = RGB2YUV(w[6]);\n\\r
- YUV[7] = RGB2YUV(w[7]);\n\\r
- YUV[8] = RGB2YUV(w[8]);\n\\r
-\\r
- int pattern = 0;\n\\r
- int flag = 1;\n\\r
- int k;\n\\r
- for (k = 0; k < 9; k++)\n\\r
- {\n\\r
- if (k == 4) continue;\n\\r
-\\r
- if (Diff(YUV[4], YUV[k]))\n\\r
- {\n\\r
- pattern = pattern + flag;\n\\r
- }\n\\r
- flag = flag * 2;\n\\r
- }\n\\r
-\\r
- // k is now diff.\n\\r
- k = 0;\n\\r
- if (Diff(YUV[1], YUV[5]))\n\\r
- {\n\\r
- k = k + 1;\n\\r
- }\n\\r
- if (Diff(YUV[5], YUV[7]))\n\\r
- {\n\\r
- k = k + 2;\n\\r
- }\n\\r
- if (Diff(YUV[7], YUV[3]))\n\\r
- {\n\\r
- k = k + 4;\n\\r
- }\n\\r
- if (Diff(YUV[3], YUV[1]))\n\\r
- {\n\\r
- k = k + 8;\n\\r
- }\n\\r
-\\r
- vec2 lookupMax = vec2(1.0/47.0, 1.0/4095);\n\\r
- ivec2 thisPixel = ivec2(fract(c5 * mmTextureSize) * 4.0);\n\\r
- ivec2 lookupIndex = ivec2((thisPixel.y*4+thisPixel.x)*3, (pattern * 16) + k);\n\\r
- vec4 weight1 = texture2D(mmLookup, lookupIndex * lookupMax);\n\\r
- vec4 weight2 = texture2D(mmLookup, (lookupIndex + ivec2(1, 0)) * lookupMax);\n\\r
- vec4 weight3 = texture2D(mmLookup, (lookupIndex + vec2(2, 0)) * lookupMax);\n\\r
-\\r
- // Dodgy fix for inaccurate lookups :-( Seemed to work on Quadro NVS 290 without this\n\\r
- float allW = dot(\n\\r
- vec4(1,1,1,0),\n\\r
- vec4(\n\\r
- dot(weight1, vec4(1,1,1,0)),\n\\r
- dot(weight2, vec4(1,1,1,0)),\n\\r
- dot(weight3, vec4(1,1,1,0)),\n\\r
- 0\n\\r
- )\n\\r
- );\n\\r
- fragColour =\n\\r
- w[0]*weight1.x + w[1]*weight1.y + w[2]*weight1.z +\n\\r
- w[3]*weight2.x + w[4]*weight2.y + w[5]*weight2.z +\n\\r
- w[6]*weight3.x + w[7]*weight3.y + w[8]*weight3.z;\n\\r
- fragColour = fragColour / allW;\n\\r
-\\r
-//if (allW > 1.100) { fragColour = vec4(0.0,0.0,1.0,0); } \n\\r
-//if (allW < 0.9) { fragColour = vec4(0.0,1.0,0.0,0); } \n\\r
-\\r
-}\n\\r
-";\r
-\r
-void abortGLError()\r
-{\r
- std::cerr << "Fatal OpenGL error in hq4x shader: " <<\r
- gluErrorString(glGetError()) << std::endl;\r
- abort();\r
-}\r
-\r
-void abortShaderError(GLint shader)\r
-{\r
- std::cerr << "Fatal error using hq4x shader: " <<\r
- gluErrorString(glGetError()) << std::endl;\r
-\r
- char buffer[2048];\r
- int len;\r
- glGetShaderInfoLog(shader, 2048, &len, &buffer[0]); \r
- std::cerr << buffer << std::endl;\r
-\r
- abort();\r
-}\r
-\r
-void abortProgramError(GLint program)\r
+Hq4x::Hq4x()\r
{\r
- std::cerr << "Fatal error using hq4x program: " <<\r
- gluErrorString(glGetError()) << std::endl;\r
-\r
- char buffer[2048];\r
- int len;\r
- glGetProgramInfoLog(program, 2048, &len, &buffer[0]); \r
- std::cerr << buffer << std::endl;\r
-\r
- abort();\r
-}\r
-\r
-} // namespace\r
-\r
-\r
-Hq4x::Hq4x(size_t textureWidth, size_t textureHeight) :\r
- m_textureWidth(textureWidth),\r
- m_textureHeight(textureHeight)\r
-{\r
- m_vertexShader = glCreateShader(GL_VERTEX_SHADER);\r
- m_fragShader = glCreateShader(GL_FRAGMENT_SHADER);\r
-\r
- if (!m_vertexShader || !m_fragShader) abortGLError();\r
-\r
- glShaderSource(m_vertexShader, 1, &s_VectorSource, NULL);\r
- glShaderSource(m_fragShader, 1, &s_FragmentSource, NULL);\r
-\r
- glCompileShader(m_vertexShader);\r
- if (glGetError() != GL_NO_ERROR) abortShaderError(m_vertexShader);\r
- glCompileShader(m_fragShader);\r
- if (glGetError() != GL_NO_ERROR) abortShaderError(m_fragShader);\r
-\r
- m_program = glCreateProgram();\r
- if (!m_program) abortGLError();\r
-\r
- glAttachShader(m_program, m_vertexShader);\r
- if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
- glAttachShader(m_program, m_fragShader);\r
- if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
- glLinkProgram(m_program);\r
- if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
-\r
- glGenTextures(1, &m_lookupTexture);\r
- glActiveTexture(GL_TEXTURE0);\r
- glBindTexture(GL_TEXTURE_2D, m_lookupTexture);\r
-\r
- // Only nearest neighbour will work\r
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\r
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\r
-\r
- // Create the texture image\r
- glTexImage2D(\r
- GL_TEXTURE_2D,\r
- 0,\r
- GL_RGBA,\r
- 48,\r
- 4096,\r
- 0,\r
- GL_RGBA,\r
- GL_UNSIGNED_BYTE,\r
- getHq4xLookupTable()\r
- );\r
- glUseProgram(m_program);\r
- if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
-\r
- glActiveTexture(GL_TEXTURE1);\r
- glBindTexture(GL_TEXTURE_2D, m_lookupTexture);\r
- GLint tex = glGetUniformLocation(m_program, "mmLookup");\r
- glUniform1i(tex, 1); // Bind Texture 1\r
-}\r
-\r
-void\r
-Hq4x::prepareShader(GLuint texture)\r
-{\r
- glUseProgram(m_program);\r
- if (glGetError() != GL_NO_ERROR) abortProgramError(m_program);\r
-\r
- glBindFragDataLocationEXT(m_program,0,"fragColor");\r
-\r
- GLint size(glGetUniformLocation(m_program, "mmTextureSize"));\r
- glUniform4f(size, m_textureWidth, m_textureHeight, 0, 0);\r
-\r
- glActiveTexture(GL_TEXTURE0);\r
- glBindTexture(GL_TEXTURE_2D, texture);\r
- GLint tex(glGetUniformLocation(m_program, "mmTexture"));\r
- glUniform1i(tex, 0); // Bind Texture 0\r
-\r
-/*\r
- glActiveTexture(GL_TEXTURE1);\r
- glBindTexture(GL_TEXTURE_2D, m_lookupTexture);\r
- tex = glGetUniformLocation(m_program, "mmLookup");\r
- glUniform1i(tex, 1); // Bind Texture 1\r
-*/\r
}\r
\r
//--------------------------------------------------\r
\r
\r
\r
-uint32_t*\r
+Hq4x::Pixel*\r
Hq4x::getHq4xLookupTable() const\r
{\r
- static uint32_t* s_table(0);\r
+ static Pixel* s_table(0);\r
\r
if (s_table)\r
{\r
}\r
else\r
{\r
- s_table = new uint32_t[4096*48];\r
+ s_table = new Pixel[4096*48];\r
\r
for (int pattern = 0; pattern < 256; ++pattern)\r
{\r
);\r
\r
\r
-// TODO byteorder fixes here!\r
- s_table[index] =\r
- (w.w1 << 0) |\r
- (w.w2 << 8) |\r
- (w.w3 << 16);\r
-\r
-\r
- s_table[index + 1] =\r
- (w.w4 << 0) |\r
- (w.w5 << 8) |\r
- (w.w6 << 16);\r
+ s_table[index].r = w.w1;\r
+ s_table[index].g = w.w2;\r
+ s_table[index].b = w.w3;\r
+ s_table[index].a = 0;\r
\r
- s_table[index + 2] =\r
- (w.w7 << 0) |\r
- (w.w8 << 8) |\r
- (w.w9 << 16);\r
+ s_table[index + 1].r = w.w4;\r
+ s_table[index + 1].g = w.w5;\r
+ s_table[index + 1].b = w.w6;\r
+ s_table[index + 1].a = 0;\r
\r
-//hmm, order appears to be ABGR\r
-/*\r
- s_table[index] = 0x0090FF10;\r
- s_table[index+1] = 0x00;\r
- s_table[index+2] = 0x00;\r
-*/\r
+ s_table[index + 2].r = w.w7;\r
+ s_table[index + 2].g = w.w8;\r
+ s_table[index + 2].b = w.w9;\r
+ s_table[index + 2].a = 0;\r
}\r
}\r
}\r
#include <cstdint>
-#include <GL/gl.h>
-
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;
};
}
-Subproject commit f275ba42be259b90a32a5b7f9399bc175c6e4c4e
+Subproject commit b9f320b74352a88649f498af9a9fc608fa45d558
}
elsif ($reader->name eq "instruction")
{
- processInstruction($subtree)
+ expandInstruction($subtree)
}
else
{
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;
$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');
$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)
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);
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";
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#define GL_GLEXT_PROTOTYPES
+
+#include "glBoy.hh"
+#include "GLGraphics.hh"
+#include "Log.hh"
+
+#include <GL/gl.h>
+#include <GL/glext.h>
+#include <SDL/SDL_opengl.h>
+#include <SDL/SDL_video.h>
+
+#include <SDL/SDL_opengl.h>
+
+#include <cassert>
+
+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();
+}
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_GLGRAPHICS_HH
+#define GLBOY_GLGRAPHICS_HH
+
+#include "glBoy.hh"
+#include "Core.hh"
+#include "GameboyGraphics.hh"
+
+#include "hq4x.hh"
+
+#include <SDL/SDL.h>
+#include <SDL/SDL_opengl.h>
+
+#include <vector>
+
+#include <pthread.h>
+
+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
+
// You should have received a copy of the GNU General Public License
// along with glBoy. If not, see <http://www.gnu.org/licenses/>.
-#define GLX_GLXEXT_PROTOTYPES 1
#include "glBoy.hh"
#include "Core.hh"
#include "GameboyCart.hh"
#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 <GL/glext.h>
#include <SDL/SDL.h>
-#include <SDL/SDL_opengl.h>
-#include <SDL/SDL_video.h>
#include <libconfig.h++>
-#include <zipper.hh>
#include <cassert>
#include <set>
#include <unistd.h>
#include <pwd.h>
+#include <pthread.h>
+
namespace
{
enum Constants
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 <<
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());
}
}
}
+}
+extern "C"
+{
+ static void* start(void* val)
+ {
+ glBoy::Core* core = reinterpret_cast<glBoy::Core*>(val);
+ core->run();
+ return NULL;
+ }
}
int main(int argc, char** argv)
+{
{
std::cout << PACKAGE_STRING << " <" << PACKAGE_BUGREPORT << ">" << std::endl;
libconfig::Config config;
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);
{
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':
{
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':
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);
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());
// Most interaction between GB hardware and the program occurs here.
{
std::shared_ptr<glBoy::MemoryMap::Memory> mem(new glBoy::RAM(128));
- map.map(0xFF80, 128, mem);
- }
-
- // Map the Interrupt flags. Currently handled in Core.cc
- {
- std::shared_ptr<glBoy::MemoryMap::Memory> 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<glBoy::GameboyJoypad> joypad(
- new glBoy::GameboyJoypad(core, config)
+ new glBoy::GameboyJoypad(core)
);
map.map(0xFF00, 1, joypad);
std::shared_ptr<glBoy::GameboyTimer> timer(
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();
}
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#include "glBoy.hh"
+#include "SDLKeys.hh"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdlib>
+
+using namespace glBoy;
+
+namespace
+{
+ void setConfiguredKey(
+ const std::map<std::string, int>& keyMap,
+ const libconfig::Config& config,
+ const std::string& configParam,
+ int& mappedKey
+ )
+ {
+ if (config.exists(configParam))
+ {
+ std::string value;
+ config.lookupValue(configParam, value);
+ std::map<std::string, int>::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<std::string, int> sdlKeyMap;
+
+ for (int i = SDLK_FIRST; i < SDLK_LAST; ++i)
+ {
+ std::string keyName = SDL_GetKeyName(static_cast<SDLKey>(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
+ );
+}
+
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_SDLKEYS_HH
+#define GLBOY_SDLKEYS_HH
+
+#include "glBoy.hh"
+#include "Core.hh"
+#include "GameboyJoypad.hh"
+
+#include <libconfig.h++>
+#include <SDL/SDL.h>
+
+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
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#include "glBoy.hh"
+#include "SDLSound.hh"
+
+#include <cassert>
+#include <cstdlib>
+#include <cmath>
+
+using namespace glBoy;
+
+extern "C"
+{
+ void mixaudio(void *userData, Uint8 *stream, int len)
+ {
+ GameboySound* ourObject(reinterpret_cast<GameboySound*>(userData));
+ ourObject->mixSounds(reinterpret_cast<int16_t*>(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);
+}
+
--- /dev/null
+// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+#ifndef GLBOY_SDLSOUND_HH
+#define GLBOY_SDLSOUND_HH
+
+#include "glBoy.hh"
+#include "GameboySound.hh"
+
+#include <SDL/SDL.h>
+
+namespace glBoy
+{
+
+class SDLSound
+{
+public:
+ SDLSound(GameboySound& gameboySound);
+
+ void mixSounds(Uint8* stream, int len);
+private:
+};
+
+} // namespace glBoy
+
+#endif