From f7b590d28b1a5915e338bfe13086ea0e83d4fe79 Mon Sep 17 00:00:00 2001 From: Michael McMaster Date: Mon, 31 Jan 2011 21:37:36 +1000 Subject: [PATCH] Oops, getting this git thing wrong --- ALU.cc | 42 ++ ALU.hh | 799 +++++++++----------- Core.cc | 219 ++++-- Core.hh | 100 ++- DAA.hh | 62 ++ DWORD.hh | 187 ++--- GameboyCart.cc | 235 ++++++ GameboyCart.hh | 42 ++ GameboyGraphics.cc | 758 +++++++++++++++++++ GameboyGraphics.hh | 180 ++++- GameboyJoypad.cc | 138 ++++ GameboyJoypad.hh | 61 ++ GameboySound.cc | 492 ++++++++++++ GameboySound.hh | 190 +++++ GameboyTimer.cc | 160 ++++ GameboyTimer.hh | 64 ++ InstructionSet.opcode | 1656 +++++++++++++++++++++++++++++++++++++++-- Log.cc | 72 ++ Log.hh | 62 ++ Main.cc | 283 +++++++ Makefile | 69 +- MemoryMap.cc | 204 ++++- MemoryMap.hh | 121 +-- RAM.cc | 71 +- RAM.hh | 70 +- README | 29 +- ROM.cc | 47 +- ROM.hh | 34 +- Registers.cc | 91 ++- Registers.hh | 119 +-- Resample.cc | 0 Test.cc | 275 +++++++ glBoy.hh | 43 +- hq4x.cc | 447 ++++++++--- hq4x.hh | 50 ++ makeOpcodes.pl | 393 ++++++++++ shaders/scale2x.slf | 69 +- shaders/scale2x.slv | 44 +- util.hh | 63 ++ 39 files changed, 6792 insertions(+), 1249 deletions(-) mode change 100755 => 100644 Core.cc mode change 100755 => 100644 Core.hh mode change 100755 => 100644 Registers.cc mode change 100755 => 100644 Registers.hh delete mode 100644 Resample.cc diff --git a/ALU.cc b/ALU.cc index e69de29..b8291f5 100644 --- a/ALU.cc +++ b/ALU.cc @@ -0,0 +1,42 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "ALU.hh" + +using namespace glBoy; + +void ALU::load() {} + +const uint8_t ALU::ParityFlagLookup[256] = +{ +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04, 0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, +0x04, 0, 0, 0x04, 0, 0x04, 0x04, 0, 0, 0x04, 0x04, 0, 0x04, 0, 0, 0x04 +}; diff --git a/ALU.hh b/ALU.hh index a59402e..aa51fda 100755 --- a/ALU.hh +++ b/ALU.hh @@ -1,431 +1,38 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -r = r2; - - - - -r = n; - - - - -r = m_mem.read8(m_reg.HL); - - - - -r = m_mem.read8(m_reg.IX, d); - - - - -r = m_mem.read8(m_reg.IY, d); - - - - -m_mem.write8(m_reg.HL, r); - - - - -m_mem.write8(m_reg.IX, d, r); - - - - -m_mem.write8(m_reg.IY, d, r); - - - - -m_mem.write8(m_reg.HL, n); - - - - -m_mem.write8(m_reg.IX, d, n); - - - - -m_mem.write8(m_reg.IY, d, n); - - - - -m_reg.A = m_mem.read8(m_reg.BC); - - - - -m_reg.A = m_mem.read8(m_reg.DE); - - - - -m_reg.A = m_mem.read8(N); - - - - -m_mem.write8(m_reg.BC, m_reg.A); - - - - -m_mem.write8(m_reg.DE, m_reg.A); - - - - -m_mem.write8(N, m_reg.A); - - - - -m_reg.F.set(m_reg.I, *this); -m_reg.A = m_reg.I; - - - - -m_reg.F.set(m_reg.R, *this); -m_reg.A = m_reg.R; - - - - -m_reg.I = m_reg.A; - - - - -m_reg.R = m_reg.A; - - - - - - -d = N; - - - - -m_reg.IX = N; - - - - -m_reg.IY = N; - - - - -m_reg.HL = m_mem.read16(N); - - - - -d = m_mem.read16(N); - - - - -m_reg.IX = m_mem.read16(N); - - - - -m_reg.IY = m_mem.read16(N); - - - - -m_mem.write16(N, m_reg.HL); - - - - -m_mem.write16(N, d); - - - - -m_mem.write16(N, m_reg.IX); - - - - -m_mem.write16(N, m_reg.IY); - - - - -m_reg.SP = m_reg.HL; - - - - -m_reg.SP = m_reg.IX; - - - - -m_reg.SP = m_reg.IY; - - - - -m_reg.SP.dec(2); -m_mem.write16(m_reg.SP, q); - - - - -m_reg.SP.dec(2); -m_mem.write16(m_reg.SP, m_reg.IX); - - - - -m_reg.SP.dec(2); -m_mem.write16(m_reg.SP, m_reg.IY); - - - - -q = m_mem.read16(m_reg.SP); -m_reg.SP.inc(2); - - - - -m_reg.IX = m_mem.read16(m_reg.SP); -m_reg.SP.inc(2); - - - - -m_reg.IY = m_mem.read16(m_reg.SP); -m_reg.SP.inc(2);; - - - - - - - - DWORD tmp(m_reg.DE); - m_reg.DE = m_reg.HL; - m_reg.HL = tmp; - - - - - DWORD tmp(m_reg.AF); - m_reg.AF = m_regDash.AF; - m_regDash.AF = tmp; - - - - - DWORD tmpBC(m_reg.BC); - DWORD tmpDE(m_reg.BC); - DWORD tmpHL(m_reg.BC); - m_reg.BC = m_regDash.BC; - m_reg.DE = m_regDash.DE; - m_reg.HL = m_regDash.HL; - m_regDash.BC = tmpBC; - m_regDash.DE = tmpDE; - m_regDash.HL = tmpHL; - - - - - DWORD tmp(m_reg.HL); - m_reg.HL = m_mem.read16(m_reg.SP); - m_mem.write16(m_reg.SP, tmp); - - - - - DWORD tmp(m_reg.IX); - m_reg.IX = m_mem.read16(m_reg.SP); - m_mem.write16(m_reg.SP, tmp); - - - - - DWORD tmp(m_reg.IY); - m_reg.IY = m_mem.read16(m_reg.SP); - m_mem.write16(m_reg.SP, tmp); - - - - - m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); - m_reg.DE.inc(); - m_reg.HL.inc(); - m_reg.BC.dec(); - m_reg.F.setCounter(m_reg.BC); - - - - - do - { - m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); - m_reg.DE.inc(); - m_reg.HL.inc(); - m_reg.BC.dec(); - - clock += 21; // Increment for each loop iteration - interrupt(); // Check for interrupts at each iteration - } while (m_reg.BC.host() != 0); - m_reg.F.setCounter(m_reg.BC); - clock += 16; // Increment again when leaving loop. - - - - - m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); - m_reg.DE.dec(); - m_reg.HL.dec(); - m_reg.BC.dec(); - m_reg.F.setCounter(m_reg.BC); - - - - - do - { - m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); - m_reg.DE.dec(); - m_reg.HL.dec(); - m_reg.BC.dec(); - - clock += 21; // Increment for each loop iteration - interrupt(); // Check for interrupts at each iteration - } while (m_reg.BC.host() != 0); - m_reg.F.setCounter(m_reg.BC); - clock += 16; // Increment again when leaving loop. - - - - - m_reg.BC.dec(); - m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC); - m_reg.HL.inc(); - - - - - bool equal; - do - { - m_reg.BC.dec(); - m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC); - equal = (m_reg.A == m_reg.read8(r_reg.HL)); - m_reg.HL.inc(); - - clock += 21; // Increment for each loop iteration - interrupt(); // Check for interrupts at each iteration - } while (m_reg.BC.host() != 0 && !equal); - clock += 16; // Increment again when leaving loop. - - - - - - m_reg.BC.dec(); - m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC); - m_reg.HL.dec(); - - - - - bool equal; - do - { - m_reg.BC.dec(); - m_reg.F.setSub(m_reg.A, m_reg.read8(r_reg.HL), m_reg.BC); - equal = (m_reg.A == m_reg.read8(r_reg.HL)); - m_reg.HL.dec(); - - clock += 21; // Increment for each loop iteration - interrupt(); // Check for interrupts at each iteration - } while (m_reg.BC.host() != 0 && !equal); - clock += 16; // Increment again when leaving loop. - - +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_ALU_HH +#define GLBOY_ALU_HH + +#include "glBoy.hh" +#include "DWORD.hh" +#include "Registers.hh" + +namespace glBoy +{ +namespace ALU +{ +void load(); +extern const uint8_t ParityFlagLookup[256]; -op8_t doAdd8(op8_t a, op8_t b, Flags& flags, bool carry) +inline uint8_t +add(uint8_t a, uint8_t b, Flags& flags, bool carry = false) { - op8_t result(a + b); + uint8_t result(a + b); if (carry && flags.C) { @@ -449,39 +56,309 @@ op8_t doAdd8(op8_t a, op8_t b, Flags& flags, bool carry) ((((~(a ^ b)) & (result ^ a)) & 0x80) >> 5) |// P // N - (((op16_t(a) + op16_t(b)) & 0x100) >> 8) // C + (((uint16_t(a) + uint16_t(b)) & 0x100) >> 8); // C + + return result; +} + +inline uint8_t +adc(uint8_t a, uint8_t b, Flags& flags) +{ + return add(a, b, flags, true); +} + +inline DWORD +add(DWORD a, DWORD b, Flags& flags, bool carry = false) +{ + const uint16_t aHost(a.host()); + const uint16_t bHost(b.host()); + + uint16_t result(aHost + bHost); + + if (carry && flags.C) + { + ++result; + } + + if (carry) + { + // Flags only affected for adc. + flags.S = (result & 0x8000) ? 1 : 0; + flags.Z = (result == 0); + flags.P = (((~(aHost ^ bHost)) & (result ^ aHost)) & 0x8000) ? 1 : 0; + + } + + flags.N = 0; + flags.H = ((aHost ^ bHost ^ result) >> 10) & 0x1; + flags.C = (((uint32_t(aHost) + uint32_t(bHost)) & 0x10000) >> 16); + + return glBoy::htoz(result); +} + +inline DWORD +adc(DWORD a, DWORD b, Flags& flags) +{ + return add(a, b, flags, true); +} + +inline uint8_t +sub(uint8_t a, uint8_t b, Flags& flags, bool borrow = false) +{ + uint8_t c = (borrow && flags.C) ? 1 : 0; + uint8_t result(a - b - c); + + flags.byte = + (result & 0x80) | // S MSB + ((result == 0) ? 0x40 : 0) | // Z + // U5 TODO + (((a & 0xf) - (b & 0xf)) & 0x10) | // H + // U3 TODO + + // Detected signed overflow + ((int16_t(static_cast(a)) - + int16_t(static_cast(b)) - + c + ) == + static_cast(result) ? 0 : 0x4) | // P + + 0x2 | // N + (((uint16_t(a) - uint16_t(b)) & 0x100) >> 8); // C + + return result; +} + +inline uint8_t +sbc(uint8_t a, uint8_t b, Flags& flags) +{ + return sub(a, b, flags, true); +} + +inline DWORD +sub(DWORD a, DWORD b, Flags& flags, bool borrow = false) +{ + const uint16_t aHost(a.host()); + const uint16_t bHost(b.host()); + + uint16_t c = (borrow && flags.C) ? 1 : 0; + uint16_t result(aHost - bHost - c); + + flags.byte = + ((result & 0x8000) >> 8) | // S MSB + ((result == 0) ? 0x40 : 0) | // Z + // U5 TODO + ((((aHost & 0xf00) - (bHost & 0xf00)) & 0x1000) >> 8) | // H + // U3 TODO + + // Detected signed overflow + ((int32_t(static_cast(aHost)) - + int32_t(static_cast(bHost)) - + c + ) == + static_cast(result) ? 0 : 0x4) | // P + + 0x2 | // N + (((uint32_t(aHost) - uint32_t(bHost)) & 0x10000) >> 16); // C + + return htoz(result); +} + +inline DWORD +sbc(DWORD a, DWORD b, Flags& flags) +{ + return sub(a, b, flags, true); } - - - - m_reg.A = doAdd8(m_reg.A, r, m_reg.F, false); - - - - - m_reg.A = doAdd8(m_reg.A, n, m_reg.F, false); - - - - - m_reg.A = doAdd8(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F, false); - - - - - m_reg.A = doAdd8(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F, false); - - - - - m_reg.A = doAdd8(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F, false); - - - - - m_reg.A = doAdc8(m_reg.A, r, m_reg.F, true); - - - + +inline uint8_t +bitwiseAnd(uint8_t a, uint8_t b, Flags& flags) +{ + uint8_t result(a & b); + flags.byte = + (result & 0x80) | // S + ((result == 0) ? 0x40 : 0) | // Z + // U5 + 0x10 | // H + // U3 + ParityFlagLookup[result]; // P + // N + // C + return result; +} + +inline uint8_t +bitwiseOr(uint8_t a, uint8_t b, Flags& flags) +{ + uint8_t result(a | b); + flags.byte = + (result & 0x80) | // S + ((result == 0) ? 0x40 : 0) | // Z + // U5 + // H + // U3 + ParityFlagLookup[result]; // P + // N + // C + return result; +} + +inline uint8_t +bitwiseXor(uint8_t a, uint8_t b, Flags& flags) +{ + uint8_t result(a ^ b); + flags.byte = + (result & 0x80) | // S + ((result == 0) ? 0x40 : 0) | // Z + // U5 + // H + // U3 + ParityFlagLookup[result]; // P + // N + // C + return result; +} + +inline uint8_t +rotateLeft(uint8_t a, Flags& flags, bool throughCarry, bool setAllFlags = false) +{ + uint8_t msb(a >> 7); + a <<= 1; + + if (throughCarry) + { + a |= flags.C; + } + else + { + a |= msb; + } + flags.H = 0; + flags.N = 0; + flags.C = msb; + + if (setAllFlags) + { + flags.byte |= (a & 0x80); + flags.Z = (a == 0) ? 1 : 0; + flags.byte |= ParityFlagLookup[a]; + } +// TODO U3 U5 + + return a; +} + +inline uint8_t +shiftLeft(uint8_t a, Flags& flags) +{ + uint8_t msb(a >> 7); + a <<= 1; + + flags.byte = + (a & 0x80) | // S + ((a == 0) ? 0x40 : 0) | // Z + // U5 + // H + // U3 + ParityFlagLookup[a] | // P + // N + msb; // C + + return a; +} + +inline uint8_t +rotateRight(uint8_t a, Flags& flags, bool throughCarry, bool setAllFlags = false) +{ + uint8_t lsb(a & 0x1); + a >>= 1; + + if (throughCarry) + { + a |= flags.C ? 0x80 : 0; + } + else + { + a |= lsb ? 0x80 : 0; + } + flags.H = 0; + flags.N = 0; + flags.C = lsb; + + if (setAllFlags) + { + flags.byte |= (a & 0x80); + flags.Z = (a == 0) ? 1 : 0; + flags.byte |= ParityFlagLookup[a]; + } +// TODO U3 U5 + + return a; +} + +inline uint8_t +shiftRight(uint8_t a, Flags& flags, bool arithmatic) +{ + uint8_t lsb(a & 0x1); + uint8_t msb(a & 0x80); + a >>= 1; + + // Restore sign bit + if (arithmatic) a |= msb; + + flags.byte = + (a & 0x80) | // S + ((a == 0) ? 0x40 : 0) | // Z + // U5 + // H + // U3 + ParityFlagLookup[a] | // P + // N + lsb; // C + + return a; +} + +inline void +RLD(uint8_t& a, uint8_t& m, Flags& f) +{ + // Whoever came up with RLD was on crack. + + uint8_t newM = (a & 0xf) | (m << 4); + a = (a & 0xF0) | (m >> 4); + m = newM; + + f.byte = + (a & 0x80) | // S + ((a == 0) ? 0x40 : 0) | // Z + // U5 + // H + // U3 + ParityFlagLookup[a]; // P + // N + // C +} + +inline void +RRD(uint8_t& a, uint8_t& m, Flags& f) +{ + // Whoever came up with RRD was on crack. + + uint8_t newM = (a << 4) | (m >> 4); + a = (a & 0xF0) | (m & 0xf); + m = newM; + + f.byte = + (a & 0x80) | // S + ((a == 0) ? 0x40 : 0) | // Z + // U5 + // H + // U3 + ParityFlagLookup[a]; // P + // N + // C +} + +} // namespace ALU + +} // namespace m80 +#endif + diff --git a/Core.cc b/Core.cc old mode 100755 new mode 100644 index 32aa87c..9e8224e --- a/Core.cc +++ b/Core.cc @@ -1,83 +1,188 @@ -/** - * Emulated Z80 CPU. - * - * Incrementing elapsed cycles is performed separately from emulating the opcodes. - * to simplify the emulation. Cycle info could be embedded in the switch statement? - * eg. case X: doFoo; cycles += 7 - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ -#include "bits/Z80.hh" +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" #include "Core.hh" +#include "ALU.hh" +#include "DAA.hh" +#include "Log.hh" +#include "util.hh" + +#include +#include +#include -using namespace Z80; +using namespace glBoy; -Z80::Core(std::auto_ptr mem) : +Core::Core() : m_iff1(0), m_iff2(0), - m_mem(mem), + m_halted(false), + m_clock(0), + m_nextPeriodicCallback(std::numeric_limits::max()) { + m_reg.reset(); + ALU::load(); } - public void run(CycleCount maxCycles) - { - CycleCount elapsedCycles = 0; +void +Core::raiseInterrupt(int interrupt) +{ + if (m_iff1 != 0) // Are interrupts enabled ? + { + // TODO this interrupt handling is GB80 specific + bool enabled(false); - while (elapsedCycles < maxCycles) - { - op8_t opcode = op8(); - switch (opcode) - { - } - } - } + uint8_t flags(m_mem.read8(htoz(0xFFFF))); + uint8_t outFlags(m_mem.read8(htoz(0xFF0F))); -op8_t Core::op8() -{ - return m_mem.read8(m_reg.PC++); + switch (interrupt) + { + case 0x40: enabled = flags & 0x1; outFlags |= 0x1; break; + case 0x48: enabled = flags & 0x2; outFlags |= 0x2; break; + case 0x50: enabled = flags & 0x4; outFlags |= 0x4; break; + case 0x58: enabled = flags & 0x8; outFlags |= 0x8; break; + case 0x60: enabled = flags & 0x10; outFlags += 0x10; break; + } + if (enabled) + { + m_interruptQueue.push_back(interrupt); + } + } } -op16_t Core::op16() +void +Core::run() { - op16_t operand = m_mem.read16(m_reg.PC); - m_reg.PC += 2; - return operand; -} +#include "InstructionSet_Tables.cc" -void Core::reg8(op8_t opcode, u8_t value) -{ - // 00RRR000 - reg8(opcode >> 3) = value; + // Instruction decoding occurs in an odd order for the + // double-prefix instructions. PREFIX PREFIX OPERAND OPCODE + // This operand value is set in the dispatch method of the double-opcode + // handler + // TODO z80 only. Unused for gb80 uint8_t doublePrefixOperand(0); + + Log& log(Log::Instance()); + + while (true) + { + cycle_t clock = 0; + + uint16_t prefix(0); // Used for debug logging only. + + // Check for interrupts. + if (!m_interruptQueue.empty()) + { + int interrupt = m_interruptQueue.front(); + m_interruptQueue.pop_front(); + + m_iff1 = 0; + m_iff2 = 0; + + // TODO interrupt modes! + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + + m_reg.PC = htoz(interrupt); + + m_halted = false; + } + else if (!m_halted) + { + uint16_t opcodePC = m_reg.PC.host(); // For debugging + uint8_t opcode = op8(); + + goto *(OpcodeTable[opcode]); + +#include "InstructionSet_Opcodes.cc" + +BAD_OPCODE: + log(Log::DBG_ERROR) << + "Unknown Opcode: " << glBoy::hex(opcode) << " (PC=" << opcodePC << ")" << + std::endl; + assert(false); + +END_INSTRUCTIONS: ; + } + else + { + clock = 4; + } + + m_clock += clock; + + if (m_halted && m_interruptQueue.empty()) + { + m_clock = std::max(m_clock, m_nextPeriodicCallback); + } + + if (m_clock >= m_nextPeriodicCallback) + { + m_nextPeriodicCallback = std::numeric_limits::max(); + for (size_t i = 0; i < m_periodicCallbacks.size(); ++i) + { + if (m_clock >= m_periodicCallbacks[i].nextPeriod) + { + m_periodicCallbacks[i].callback(i); + + while (m_periodicCallbacks[i].nextPeriod <= m_clock) + { + m_periodicCallbacks[i].nextPeriod += + m_periodicCallbacks[i].clocks; + } + } + + if (m_periodicCallbacks[i].nextPeriod < m_nextPeriodicCallback) + { + m_nextPeriodicCallback = m_periodicCallbacks[i].nextPeriod; + } + } + } + } } -reg8_t& Core::reg8(op8_t opcode) const +Core::CallbackId +Core::registerPeriodic( + int clockPeriod, int offset, const PeriodicCallbackFn& callback + ) { - // 00000RRR - switch (opcode & 0b00000111) + assert(m_clock == 0); + PeriodicCallback periodic = { clockPeriod, offset, callback }; + + if (periodic.nextPeriod < m_nextPeriodicCallback) { - case 0: return m_reg.B; - case 1: return m_reg.C; - case 2: return m_reg.D; - case 3: return m_reg.E; - case 4: return m_reg.H; - case 5: return m_reg.L; - case 6: assert(0); FIXME - case 7: return m_reg.A; + m_nextPeriodicCallback = periodic.nextPeriod; } + + m_periodicCallbacks.push_back(periodic); + return m_periodicCallbacks.size() - 1; } - -reg16_t& Core::dd(op8_t opcode) const + +uint8_t Core::op8() { - // 00dd0000 - u8_t offset = (opcode & 0b00110000) >> 4; - return (offset == 3) ? m_reg.SP : *(m_reg.begin16 + offset); + uint8_t result(m_mem.read8(m_reg.PC)); + m_reg.PC.inc(); + return result; } -reg16_t& Core::qq(op8_t opcode) const +DWORD Core::op16() { - // 00qq0000 - u8_t offset = (opcode & 0b00110000) >> 4; - return *(m_reg.begin16 + offset); + DWORD operand(m_mem.read16(m_reg.PC)); + m_reg.PC.inc(2); + return operand; } diff --git a/Core.hh b/Core.hh old mode 100755 new mode 100644 index 0dbbadb..167fbe5 --- a/Core.hh +++ b/Core.hh @@ -1,41 +1,64 @@ -/** - * Emulated Z80 CPU. - * - * Incrementing elapsed cycles is performed separately from emulating the opcodes. - * to simplify the emulation. Cycle info could be embedded in the switch statement? - * eg. case X: doFoo; cycles += 7 - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ -#pragma once - -#include "bits/Z80.hh" -#include "Clock.hh" - -#include - -namespace Z80 +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_CORE_HH +#define GLBOY_CORE_HH + +#include "glBoy.hh" +#include "DWORD.hh" +#include "MemoryMap.hh" +#include "Registers.hh" + +#include +#include +#include +#include + +namespace glBoy { class Core { public: - Core(std::auto_ptr mem); + Core(); - cycle_t run(cycle_t maxCycles); + MemoryMap& getMemoryMap() { return m_mem; } + + Registers& getRegisters() { return m_reg; } + + void run(); + + cycle_t getClockCount() const { return m_clock; } bit getIFF2() const { return m_iff2; } -private: - op8_t op8(); - op16_t op16(); + void raiseInterrupt(int interrupt); + void halt() { m_halted = true; } - void reg8(op8_t opcode, u8_t value); - reg8_t& reg8(op8_t opcode) const; + typedef int CallbackId; + typedef std::function PeriodicCallbackFn; - reg16_t& dd(op8_t opcode) const; - reg16_t& qq(op8_t opcode) const; + CallbackId registerPeriodic( + int clockPeriod, int offset, const PeriodicCallbackFn& callback + ); + +private: + uint8_t op8(); + DWORD op16(); // The Z80 can switch between register sets. Registers m_reg; @@ -45,5 +68,26 @@ private: bit m_iff1; bit m_iff2; - std::auto_ptr m_mem; + enum InterruptMode { IM_0, IM_1, IM_2 }; + InterruptMode m_interruptMode; + + bool m_halted; + std::deque m_interruptQueue; + + MemoryMap m_mem; + + cycle_t m_clock; + + struct PeriodicCallback + { + int clocks; + cycle_t nextPeriod; + PeriodicCallbackFn callback; + }; + std::vector m_periodicCallbacks; + cycle_t m_nextPeriodicCallback; }; + +} // namespace glBoy + +#endif diff --git a/DAA.hh b/DAA.hh index e69de29..6e1c3bf 100644 --- a/DAA.hh +++ b/DAA.hh @@ -0,0 +1,62 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_DAA_HH +#define GLBOY_DAA_HH + +#include "glBoy.hh" +#include "ALU.hh" +#include "Registers.hh" + +namespace glBoy +{ + +// Algorithm taken from +// http://wikiti.brandonw.net/?title=Z80_Instruction_Set +inline uint8_t DAA(uint8_t a, Flags& flags) +{ + uint8_t result(a); + + if (flags.N) + { + if (flags.H || ((a & 0xf) > 9)) result -= 6; + if (flags.C || (a > 0x99)) result -= 0x60; + } + else + { + if (flags.H || ((a & 0xf) > 9)) result += 6; + 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 + + return result; +} + +} // namespace + +#endif diff --git a/DWORD.hh b/DWORD.hh index 0c23bd5..abf665a 100644 --- a/DWORD.hh +++ b/DWORD.hh @@ -1,71 +1,37 @@ -/** - * Emulated representation of the Z80 CPU registers. - * - * The Z80 cpu supports switching between multiple register sets. Each Register - * object only stores the details of a single register set. -* -* 16bit Register Pairs are represented in a Struct to enable simple byte-order -* conversions where required. - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ -#pragma once - -#include "mm80.hh" - -namespace mm80 +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_DWORD_HH +#define GLBOY_DWORD_HH + +#include "glBoy.hh" +#include "util.hh" + +#include + +namespace glBoy { -class Core; - -/** 8-bit packed representation of the Z80 flags register. - * - * C Carry. LSB of Flag register - * N Add/Subtract - * P (V) Parity/Overflow - * U3 3rd bit of last 8bit op that altered flags - * H Half-Carry (BCD) - * U5 5th bit of last 8bit op that altered flags - * Z Zero Flag - * S Sign Flag. MSB of Flag register - */ -struct Flags -{ - union - { - struct - { - unsigned C:1; /// Carry. LSB of Flag register - unsigned N:1; /// Add/Subtract - unsigned P:1; /// (V) Parity/Overflow - unsigned U3:1; /// 3rd bit of last 8bit op that altered flags - unsigned H:1; /// Half-Carry (BCD) - unsigned U5:1; /// 5th bit of last 8bit op that altered flags - unsigned Z:1; /// Zero Flag - unsigned S:1; /// Sign Flag. MSB of Flag register - }; - reg8_t byte; - }; - - Flags(); - - // Update the flags based on an operands value (eg. for simple assignment) - void set(op8_t operand, const Core& context); - - // Update the flags when changing BC - void setCounter(op16_t BC); - - // Set from A - B - void setSub(op8_t A, op8_t B, reg16_t BC); -}; - -struct DWORD +struct __attribute__ ((__packed__)) DWORD { // Always constructed, and stored, in Z80 byte order. union { - reg16_t dword; + uint16_t dword; struct { reg8_t l; @@ -73,41 +39,47 @@ struct DWORD }; }; - reg16_t z80() const { return dword; } - reg16_t host() const + uint16_t z80() const { return dword; } + uint16_t host() const { #ifdef HOST_LITTLE_ENDIAN return dword; #else - return (reg16_t(h) << 8) | l; + return (uint16_t(h) << 8) | l; #endif } - void inc(u8_t val = 1) const + void inc(uint8_t val = 1) { #ifdef HOST_LITTLE_ENDIAN - ++dword; + dword += val; #else - reg16_t tmp(host() + val); + uint16_t tmp(host() + val); l = tmp & 0xf; h = tmp >> 8; #endif } - void dec(u8_t val = 1) const + void dec(uint8_t val = 1) { #ifdef HOST_LITTLE_ENDIAN - --dword; + dword -= val; #else - reg16_t tmp(host() - val); + uint16_t tmp(host() - val); l = tmp & 0xf; h = tmp >> 8; #endif } }; +inline std::ostream& operator<<(std::ostream& out, const glBoy::DWORD& val) +{ + out << glBoy::hex(val.host()); + return out; +} + // Create a dword from z80-ordered mem. -static DWORD CreateDWORD(u8_t* in) +inline DWORD CreateDWORD(uint8_t* in) { DWORD result; result.l = in[0]; @@ -115,64 +87,23 @@ static DWORD CreateDWORD(u8_t* in) return result; } -/** Z80 register access. - * - * Registers may be accessed in either 8-bit (eg. A) - * or by their 16-bit pair (eg. AF) - * - */ -struct Registers +#if defined(HOST_LITTLE_ENDIAN) +inline DWORD htoz(uint16_t host) { - Registers(); - -#define Z80_REG_STRUCT(h,l) \ - struct \ - { \ - l; \ - h; \ - }; + DWORD result; + result.dword = host; + return result; +} +#else +inline DWORD htoz(uint16_t host) +{ + DWORD result; + result.dword = (host << 8) | (host >> 8); + return result; +} #endif +} // namespace glBoy - // Note: The order of these unions/structs is important, as - // offsets are taken into the Register struct - reg8_t[0] begin8; - reg16_t[0] begin16; - - union - { - Z80_REG_STRUCT(reg8_t B, reg8_t C); - DWORD BC; - }; - - union - { - Z80_REG_STRUCT(reg8_t D, reg8_t E); - DWORD DE; - }; - - union - { - Z80_REG_STRUCT(reg8_t H, reg8_t L); - DWORD HL; - }; - - union - { - Z80_REG_STRUCT(reg8_t A, Flags F,); - DWORD AF; - }; - - -#undef Z80_REG_STRUCT - - // Special Purpose - reg8_t I; // Interrupt Vector - reg8_t R; // Memory Refresh - DWORD IX; // Index Register - DWORD IY; // Index Register - DWORD SP; // Stack Pointer - DWORD PC; // Program Counter -}; +#endif -} diff --git a/GameboyCart.cc b/GameboyCart.cc index e69de29..f0566ec 100644 --- a/GameboyCart.cc +++ b/GameboyCart.cc @@ -0,0 +1,235 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" +#include "GameboyCart.hh" +#include "RAM.hh" +#include "ROM.hh" + +#include + +using namespace glBoy; +using namespace std; + +namespace +{ + class MBC1 : public MemoryMap::Memory + { + public: + MBC1(GameboyCart* parent, uint8_t* begin, uint8_t* end) : + m_parent(parent), + m_rom(begin, end), + m_mode(ROM_SELECT), + m_romLow(1), + m_romHigh(0), + m_romOffset(0) + { + } + + virtual uint8_t read8(uint16_t address) + { + if (address < 0x4000) + { + return m_rom[address]; + } + else if ((m_romOffset + address) < m_rom.size()) + { + return m_rom[m_romOffset + address]; + } + else + { +std::cerr << address << " " << m_rom.size() << " " << m_romOffset << " " << (int) m_romLow << " " << (int) m_romHigh << std::endl; +assert(false); + return 0; + } + } + + virtual void write8(uint16_t address, uint8_t value) + { + if (address < 0x1FFF) + { + // TODO enable/disable ram + } + else if (address < 0x4000) + { + m_romLow = (value == 0) ? 1 : value; + + m_romOffset = + ((((m_romHigh & 0x3) << 4) | (m_romLow & 0x1F)) - 1) * 0x4000; +assert(m_romOffset < m_rom.size()); + } + else if (address < 0x6000) + { + if (m_mode == RAM_SELECT) + { + m_parent->setRamBank(value & 0x3); + } + else + { + m_romHigh = value; + + m_romOffset = + ((((m_romHigh & 0x3) << 4) | (m_romLow & 0x1F)) - 1) * 0x4000; +assert(m_romOffset < m_rom.size()); + } + } + else + { + if (value & 1) + { + m_mode = RAM_SELECT; + } + else + { + m_mode = ROM_SELECT; + m_parent->setRamBank(0); + } + } + } + + private: + GameboyCart* m_parent; + + std::vector m_rom; + + enum Mode { ROM_SELECT, RAM_SELECT }; + Mode m_mode; + + uint8_t m_romLow; + uint8_t m_romHigh; + + uint32_t m_romOffset; + }; +} + +GameboyCart::GameboyCart( + Core& core, uint8_t* rom, size_t size + ) : + m_core(core) +{ + if (size < 16384) + { + std::cerr << "Invalid ROM (too small)" << std::endl; + abort(); + } + + bool error(false); + + string mbc; + switch (rom[0x147]) + { + case 0: + { + mbc = "None"; + + shared_ptr mem( + new MBC1(this, rom, rom + size) + ); +/* shared_ptr mem( + new ROM(rom, size) + );*/ + m_core.getMemoryMap().map(0, 0x8000, mem); + m_romBanks.push_back(mem); + }; break; + + case 1: + case 2: + case 3: + { + mbc = "MBC1"; + shared_ptr mem( + new MBC1(this, rom, rom + size) + ); + m_core.getMemoryMap().map(0, 0x8000, mem); + m_romBanks.push_back(mem); + }; + break; + default: + mbc = "Unsupported"; + error = true; + }; + + int cartRam; + switch (rom[0x149]) + { + case 0: + { + // Mis-reported on some carts. + cartRam = 0; + shared_ptr mem(new RAM(8192)); + m_core.getMemoryMap().map(0xA000, 8192, mem); + m_ramBanks.push_back(mem); + }; break; + case 1: + { + cartRam = 2048;; + + // Fill out the entire 8k, just in case. + shared_ptr mem(new RAM(8192)); + m_core.getMemoryMap().map(0xA000, cartRam, mem); + m_ramBanks.push_back(mem); + }; break; + + case 2: + { + cartRam = 8192; + shared_ptr mem(new RAM(cartRam)); + m_core.getMemoryMap().map(0xA000, cartRam, mem); + m_ramBanks.push_back(mem); + }; break; + + case 3: + { + cartRam = 32768; + for (int i = 0; i < 4; ++i) + { + shared_ptr mem(new RAM(8192)); + m_ramBanks.push_back(mem); + } + m_core.getMemoryMap().map(0xA000, 8192, m_ramBanks.front()); + }; break; + + default: + error = true; + cartRam = 0; + cerr << "Invalid RAM size" << endl; + } + + string title(rom + 0x134, rom + 0x134 + 11); + cout << "Loading Cart" << endl; + cout << "\tTitle:\t\t" << title << endl; + cout << "\tRegion:\t\t" << (rom[0x14A] ? "Worldwide" : "Japan") << endl; + cout << "\tVersion:\t" << int(rom[0x14D]) << endl; + cout << "\tMBC:\t\t" << mbc << endl; + cout << "\tROM size:\t" << (32 << rom[0x148]) << "KB" << endl; + cout << "\tExt RAM size:\t" << (cartRam / 1024) << "KB" << endl; + + if (error) + { + abort(); + } +} + +void +GameboyCart::setRamBank(size_t bank) +{ + if (bank < m_ramBanks.size()) + { + m_core.getMemoryMap().remove(0xA000); + m_core.getMemoryMap().map(0xA000, 8192, m_ramBanks[bank]); + } +} diff --git a/GameboyCart.hh b/GameboyCart.hh index e69de29..d674931 100644 --- a/GameboyCart.hh +++ b/GameboyCart.hh @@ -0,0 +1,42 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_GAMEBOYCART_HH +#define GLBOY_GAMEBOYCART_HH + +#include "glBoy.hh" +#include "Core.hh" + +namespace glBoy +{ + +class GameboyCart +{ +public: + GameboyCart(Core& core, uint8_t* rom, size_t size); + + void setRamBank(size_t bank); +private: + Core& m_core; + + std::vector > m_romBanks; + std::vector > m_ramBanks; +}; + +} // namespace glBoy + +#endif diff --git a/GameboyGraphics.cc b/GameboyGraphics.cc index e69de29..57cae8a 100644 --- a/GameboyGraphics.cc +++ b/GameboyGraphics.cc @@ -0,0 +1,758 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#define GL_GLEXT_PROTOTYPES + +#include "glBoy.hh" +#include "GameboyGraphics.hh" +#include "Log.hh" +#include "MemoryMap.hh" + +#include +#include + +#include + +#include +#include + +#include + + +using namespace glBoy; + +namespace +{ + class VRAM : public MemoryMap::Memory + { + public: + VRAM(uint8_t* mem, + const std::function& changed + ) : + m_mem(mem), + m_changed(changed) + {} + + virtual uint8_t read8(uint16_t address) { return m_mem[address]; } + virtual void write8(uint16_t address, uint8_t value) + { + m_mem[address] = value; + m_changed(); + } + + virtual void copy(uint8_t* dest, uint16_t address, int bytes) + { + memcpy(dest, m_mem + address, bytes); + } + + private: + uint8_t* m_mem; + std::function m_changed; + }; + +} + +class GameboyGraphics::GraphicsRegisters : public MemoryMap::Memory +{ +public: + uint8_t LCDC; + uint8_t STAT; + uint8_t SCY; + uint8_t SCX; + uint8_t LY; + uint8_t LYC; + // uint8_t DMA; No need to store this - write-only. + uint8_t BGP; + uint8_t OBP0; + uint8_t OBP1; + uint8_t WY; + uint8_t WX; + + GraphicsRegisters( + Core& core, + uint8_t* oam, + const std::function& backgroundChanged + ) : + LCDC(0), + STAT(0), + SCY(0), + SCX(0), + LY(0), + LYC(0), + BGP(0), + OBP0(0), + OBP1(0), + WY(0), + WX(0), + m_core(core), + m_mapping(core.getMemoryMap()), + m_oam(oam), + m_backgroundChanged(backgroundChanged) + { + } + + virtual uint8_t read8(uint16_t address) + { + assert(address < 12); + + switch (address) + { + case 0: // LCDC + { + return LCDC; + } break; + case 1: // STAT + { + return STAT; + } break; + case 2: // SCY + { + return SCY; + } break; + case 3: // SCX + { + return SCX; + } break; + case 4: // LY + { + return LY; + } break; + case 5: // LYC + { + return LYC; + } break; + case 6: // DMA + { + return 0; + } break; + case 7: // BGP + { + return BGP; + } break; + case 8: // OBP0 + { + return OBP0; + } break; + case 9: // OBP1 + { + return OBP1; + } break; + case 10: // WY + { + return WY; + } break; + case 11: // WX + { + return WX; + } break; + } + return 0; // make g++ happy. + } + + virtual void write8(uint16_t address, uint8_t value) + { + assert(address < 12); + + switch (address) + { + case 0: // LCDC + { + LCDC = value; + m_backgroundChanged(); + } break; + case 1: // STAT + { + // bits 0-2 are read only. + uint8_t tmp(STAT & 0x7); + STAT = value & 0xF8; + STAT |= tmp; + } break; + case 2: // SCY + { + SCY = value; + } break; + case 3: // SCX + { + SCX = value; + } break; + case 4: // LY + { + // Readonly. + } break; + case 5: // LYC + { + LYC = value; + } break; + case 6: // DMA + { + // Start a DMA transfer to the OAM memory area. This should + // take 160 microseconds, but there's no real need to simulate + // this. CPU may only access zero-page mem during the + // transfer, and will spin the required number of cycles. + uint16_t dmaStart(static_cast(value) << 8); + m_mapping.copy(m_oam, dmaStart, 0x9F); + } break; + case 7: // BGP + { + BGP = value; + m_backgroundChanged(); + } break; + case 8: // OBP0 + { + OBP0 = value; + } break; + case 9: // OBP1 + { + OBP1 = value; + } break; + case 10: // WY + { + WY = value; + } break; + case 11: // WX + { + WX = value; + } break; + } + } + + private: + Core& m_core; + MemoryMap& m_mapping; // For DMA transfers. + uint8_t* m_oam; + std::function m_backgroundChanged; +}; + + +GameboyGraphics::GameboyGraphics( + Core& core, + SDL_Surface* surface, + PixelScaler scaler, + ResampleMethod resampler + ) : + 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( + new GraphicsRegisters( + core, + m_oam, + std::bind(&GameboyGraphics::backgroundChanged, this) + ) + ), + m_backgroundRedraw(true), + m_surface(surface), + m_frameSkip(false), + m_lastSkipped(false), + m_stat_frames(0), + m_stat_skipped(0), + m_stat_redraw(0) +{ + MemoryMap& mapping(core.getMemoryMap()); + mapping.map( + 0x8000, + 0x2000, + std::shared_ptr( + new VRAM( + m_vram, + std::bind(&GameboyGraphics::backgroundChanged, this) + ) + ) + ); + mapping.map( + 0xFE00, + 0x100, + std::shared_ptr( + new VRAM( + m_oam, + std::bind(&GameboyGraphics::oamChanged, this) + ) + ) + ); + mapping.map(0xFF40, 12, m_reg); + + using namespace std::placeholders; + m_mode0 = core.registerPeriodic( + 456, 252, std::bind(&glBoy::GameboyGraphics::periodicCallback, this, _1) + ); + m_mode2 = core.registerPeriodic( + 456, 0, std::bind(&glBoy::GameboyGraphics::periodicCallback, this, _1) + ); + m_mode3 = core.registerPeriodic( + 456, 80, std::bind(&glBoy::GameboyGraphics::periodicCallback, this, _1) + ); + + gettimeofday(&m_stat_fpsTimer, NULL); +} + +GameboyGraphics::~GameboyGraphics() +{ + delete[] m_vram; + delete[] m_oam; +} + +void +GameboyGraphics::periodicCallback(Core::CallbackId id) +{ + // Mode 0: H-Blank + // Mode 1: V-Blank + // Mode 2: Reading from OAM + // Mode 3: Reading from OAM and VRAM + uint8_t mode; + + if (id == m_mode0) + { + renderScanLine(); + + mode = (m_reg->LY > 143) ? 1 : 0; + } + else if (id == m_mode2) + { + startScanLine(); + mode = (m_reg->LY > 143) ? 1 : 2; + } + else // mode3 + { + mode = (m_reg->LY > 143) ? 1 : 3; + } + + m_reg->STAT = (m_reg->STAT & 0xF8) | mode; +} + +void +GameboyGraphics::startScanLine() +{ + ++m_reg->LY; + if (m_reg->LY > 153) m_reg->LY = 0; + + m_lineScrollX[m_reg->LY] = m_reg->SCX; + m_lineScrollY[m_reg->LY] = m_reg->SCY; + + bool equal(m_reg->LY == m_reg->LYC); + if (equal) + { + m_reg->STAT |= 0x4; + } + else + { + m_reg->STAT &= 0xFB; + } + + if ((m_reg->LY == 144)) + { + // Draw the entire screen when entering vblank. This will be done just + // under 60 times per second. + if (!m_frameSkip || m_lastSkipped) + { + renderScreen(); + m_lastSkipped = false; + } + else + { + ++m_stat_skipped; + m_lastSkipped = true; + } + m_frameSkip = false; + displayStats(); + + //if (m_reg->STAT & 0x10) Seems to need to trigger regardless ? + // Maybe we need to trigger 0x40 as well, depending on STAT ? + { + m_core.raiseInterrupt(0x40); // Start of vblank + } + } + else if ( + (equal && (m_reg->STAT & 0x40)) || // Coincidence Interrupt + (m_reg->STAT & 0x20) // OAM interrupt + ) + { + m_core.raiseInterrupt(0x48); + } +} + +void +GameboyGraphics::renderScanLine() +{ + // Entering H-Blank + if (m_reg->STAT & 0x08 && (m_reg->LY <= 143)) + { + m_core.raiseInterrupt(0x48); + } + + // Don't bother drawing individual scanlines - just draw the entire screen + // when entering VBLANK (which is actually done in the startScanLine method) +} + +// Render the given tile to the screen at the given coordinates. +// The x and y may be outside the bounds of the screen, and may be negative. +// Only the visible part of the tile will be rendered. +void GameboyGraphics::renderTile( + uint8_t tile, + int x, + int y, + const FrameBuffer& frameBuffer + ) +{ + uint16_t tileAddr; + if (m_reg->LCDC & 0x10) + { + tileAddr = 0x8000 + static_cast(tile) * 16; + } + else + { + tileAddr = 0x9000 + + (static_cast(tile) * static_cast(16)); + } + + + renderTile(tileAddr, x, y, frameBuffer, RENDER_BLIT, m_reg->BGP); +} + +void GameboyGraphics::renderTile( + uint16_t tileAddr, + int x, + int y, + const FrameBuffer& frameBuffer, + RenderMode mode, + uint8_t palette, + uint8_t sprite, // 0x80 -> 0xA8, 0 for background/window + bool horizFlip, + bool vertFlip, + int tileHeight // 8 or 16 + ) +{ + // Render an 8x8 or 8x16 tile. + + tileAddr -= 0x8000; // Use vram directly. + + if (vertFlip) tileAddr += (tileHeight - 1) * 2; + int yStep = vertFlip ? -2 : 2; // Each row takes 2 bytes. + if (y < 0) tileAddr += yStep * (-y); + + for (int posY = (y < 0 ? -y : 0); + (posY < tileHeight) && (y + posY < frameBuffer.height); + ++posY, tileAddr += yStep + ) + { + uint8_t rowLSB(m_vram[tileAddr]); + uint8_t rowMSB(m_vram[tileAddr + 1]); + + size_t pixelRowAddr((y + posY) * frameBuffer.width + x); + + int xStep = horizFlip ? 1 : -1; + int xBit = horizFlip ? 0 : 7; + if (x < 0) xBit += xStep * (-x); + + for (int posX = (x < 0 ? -x : 0); + (posX < 8) && (x + posX < frameBuffer.width); + ++posX, xBit += xStep + ) + { + uint8_t col = + (rowMSB >> xBit) & 0x01; + col <<= 1; + col |= + (rowLSB >> xBit) & 0x01; + + Pixel& pixel = frameBuffer.frame[pixelRowAddr + posX]; + + bool display; + if (!sprite) + { + // Always display background and window data. + // window data overwrites background data + display = true; + } + else if (pixel.tileX != std::numeric_limits::max()) + { + if (col) + { + if (x < pixel.tileX) + { + display = true; + } + else + { + // Existing pixel has priority. Note that transparent + // areas of sprites are never output (ie. it remains + // background), so there is no need to check of the + // oldSprite was actually transparent. + // Also note that we draw sprites in order, so this + // sprite number can never be less than the already- + // drawn sprite (and thus will not have priority) + display = false; + } + } + else + { + // Ignore transparent areas of the sprite. + display = false; + } + } + else + { + // Should this sprite pixel overwrite the background ? + // Sprite colour 0 is transparent (shows background) + // Background colour 0 is always "behind" the sprite. + display = + col && + ((mode == RENDER_BLIT) || (pixel.colourIndex == 0)); + } + + if (display) + { + pixel.tileX = sprite ? x : std::numeric_limits::max(); + pixel.palette = palette; + pixel.colourIndex = col; + } + } + } +} + +void +GameboyGraphics::renderScreen() +{ + FrameBuffer screenBuffer = { 160, 144, &m_screen[0][0]}; + + if (m_reg->LCDC & 0x1) + { + // Draw background. Background tile map is a 256x256 virtual screen, + // that is scrolled via SCX/SCY. + + 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); + + // Render entire tile map, then copy to the screen. + memset(m_background, 0, sizeof(m_background)); + FrameBuffer buffer = { 256, 256, &m_background[0][0] }; + + for (int y = 0; y < 32; ++y) + { + uint16_t yAddr(tileMapAddr + (y * 32)); + for (int x = 0; x < 32; ++x) + { + renderTile( + m_vram[yAddr + x], + x * 8, + y * 8, + buffer + ); + } + } + } + + for (int y = 0; y < 144; ++y) + { + int scrollY = y + m_lineScrollY[y]; + if (scrollY > 255) scrollY -= 256; + + for (int x = 0; x < 160; ++x) + { + int scrollX = x + m_lineScrollX[y]; + if (scrollX > 255) scrollX -= 256; + + m_screen[y][x] = m_background[scrollY][scrollX]; + } + } + } + else + { + // clear screen. + memset(m_screen, 0, sizeof(Pixel) * 144 * 160); + } + + + if (m_reg->LCDC & 0x20) + { + // Draw window over top of existing background. + + // Draw background. Window tile map is a screen, + // that is positioned via WX/WY. + + // Select one of two maps at 0x9800/0x9C00 + uint16_t tileMapAddr(m_reg->LCDC & 0x40 ? 0x1C00 : 0x1800); + + for (int y = 0; y < 18; ++y) + { + uint16_t yAddr(tileMapAddr + (y * 32)); + + for (int x = 0; x < 20; ++x) + { + renderTile( + m_vram[yAddr + x], + x * 8 + m_reg->WX - 7, + y * 8 + m_reg->WY, + screenBuffer // Render directly to the screen. + ); + } + } + } + + std::vector sprite; + sprite.resize(160); + if (m_reg->LCDC & 0x2) + { + // Draw sprites + + for (int spriteNum = 0; spriteNum < 40; ++spriteNum) + { + Sprite sprite(getSprite(spriteNum)); + + renderTile( + 0x8000 + static_cast(sprite.tile) * 16, + sprite.x, + sprite.y, + screenBuffer, + (sprite.flags & 0x80) ? RENDER_SPRITE : RENDER_BLIT, + (sprite.flags & 0x10) ? m_reg->OBP1 : m_reg->OBP0, + static_cast(spriteNum) | 0x80, + (sprite.flags & 0x20) ? true : false, // horizontal flip + (sprite.flags & 0x40) ? true : false, // vertical flip + (m_reg->LCDC & 0x04) ? 16 : 8 // 8x8 or 8x16 + ); + } + } + updateOutputDevice(); + + +} + +GameboyGraphics::Sprite +GameboyGraphics::getSprite(int spriteNum) const +{ + Sprite result; + + result.y = static_cast(m_oam[spriteNum * 4]) - 16; + result.x = static_cast(m_oam[spriteNum * 4 + 1]) - 8; + result.tile = m_oam[spriteNum * 4 + 2]; + result.flags = m_oam[spriteNum * 4 + 3]; + + return result; +} + +void +GameboyGraphics::displayStats() +{ + m_stat_frames++; + if (m_stat_frames == 300) + { + timeval startTime(m_stat_fpsTimer); + gettimeofday(&m_stat_fpsTimer, 0); + + const double usec(1000000.0); + double fps = static_cast(m_stat_frames - m_stat_skipped) / + ( + (m_stat_fpsTimer.tv_sec + m_stat_fpsTimer.tv_usec / usec) - + (startTime.tv_sec + startTime.tv_usec / usec) + ); + + Log::Instance()(Log::DBG_STAT) << + "FPS = " << fps << " (error=" << (100 - (fps * 100 / 59.73)) << "%) " + "backround redraw=" << m_stat_redraw << std::endl; + m_stat_frames = 0; + m_stat_skipped = 0; + m_stat_redraw = 0; + } +} + +void +GameboyGraphics::blitGreyscale(uint8_t* dest) +{ + 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); + row[x] = 255 - ( colour * 85); + } + } +} + +void +GameboyGraphics:: updateOutputDevice() +{ + 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 + ); + + 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); + } +} + diff --git a/GameboyGraphics.hh b/GameboyGraphics.hh index 18afd1c..1795a6b 100644 --- a/GameboyGraphics.hh +++ b/GameboyGraphics.hh @@ -1,17 +1,125 @@ -#ifndef MM80_GAMEBOYGRAPHICS_HH -#define MM80_GAMEBOYGRAPHICS_HH +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . -#include "mm80.hh" +#ifndef GLBOY_GAMEBOYGRAPHICS_HH +#define GLBOY_GAMEBOYGRAPHICS_HH + +#include "glBoy.hh" +#include "Core.hh" #include "MemoryMap.hh" +#include "hq4x.hh" + +#include +#include + +#include + +namespace glBoy +{ class GameboyGraphics { public: + enum PixelScaler { Scaler_None, Scaler_Scale2x, Scaler_hq4x }; + enum ResampleMethod { Resample_Nearest, Resample_Bilinear }; + + GameboyGraphics( + Core& core, + SDL_Surface* surface, + PixelScaler scaler, + ResampleMethod resampler + ); + ~GameboyGraphics(); + + void startScanLine(); void renderScanLine(); + void setFrameSkip() { m_frameSkip = true; } + void clearFrameSkip() { m_frameSkip = false; } + private: + void periodicCallback(Core::CallbackId); + + void backgroundChanged() { m_backgroundRedraw = true; } + void oamChanged() {} + + struct Pixel // Must be POD - used with memset. + { + int16_t tileX; // Max for background/window + uint8_t palette; + uint8_t colourIndex; // Index into pallete. + }; + + struct FrameBuffer + { + int width; + int height; + Pixel* frame; + }; + + // RENDER_BLIT = overwrite background. + // RENDER_SPRITE = only overwrite background colour 0 (ie. send + // behind background) + enum RenderMode{ RENDER_BLIT, RENDER_SPRITE }; + + void renderScreen(); + void displayStats(); + + void renderTile( + uint8_t tile, + int x, + int y, + const FrameBuffer& frameBuffer + ); + + void renderTile( + uint16_t tileAddr, + int x, + int y, + const FrameBuffer& frameBuffer, + RenderMode mode, + uint8_t pallete, + uint8_t sprite = 0, // 0x80 -> 0xA8, 0 for background/window + bool horizFlip = false, + bool vertFlip = false, + int tileHeight = 8 // 8 or 16 + ); + + struct Sprite + { + int x; + int y; + uint8_t tile; + uint8_t flags; + }; + Sprite getSprite(int spriteNum) const; + + void blitGreyscale(uint8_t* dest); + void updateOutputDevice(); + + 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. @@ -21,46 +129,44 @@ private: // 0x9000 - 0x97FF: Tile Set #0: tiles 0-127 // 0x9800 - 0x9BFF: Tile map #0 // 0x9C00 - 0x9FFF: Tile map #1 - std::tr1::shared_ptr m_vram; + uint8_t* m_vram; - enum SpriteOptions - { - SPRITE_PALETTE = 0x10, // 0 = Pallete #0, 1 = Pallete #2 - SPRITE_XFLIP = 0x20, // 0 = Normal, 1 = flip. - SPRITE_YFLIP = 0x40, // 0 = Normal, 1 = flip - SPRITE_PRIORITY = 0x80 // 0 = Above Background, 1 = Below background - // (background colour 0 == transparent) - }; + // Object Attribute Memory. Up to 40 sprites, which reference first Tile Set + uint8_t* m_oam; // 0xFE00 - 0xFE9F - struct __attribute__ ((packed)) Sprite - { - uint8_t y; // Y-coordinate - 16 - uint8_t x; // X-coordinate - 8 - uint8_t tile; // Tile number - uint8_t options; - } + // Memory-mapped IO Registers + class GraphicsRegisters; + std::shared_ptr m_reg; - // Object Attribute Memory (Up to 40 sprites), as per Sprite struct. - std::tr1::shared_ptr m_oam; // 0xFE00 - 0xFE9F + // Raster display + bool m_backgroundRedraw; + Pixel m_background[256][256]; + Pixel m_screen[144][160]; - struct __attribute__((packed)) Registers - { - uint8_t LCDC; - uint8_t STAT; - uint8_t SCY; - uint8_t SCX; - uint8_t LY; - uint8_t LYC; - uint8_t DMA; - uint8_t OBP0; - uint8_t OBP1; - uint8_t WY; - uint8_t WX; - }; + // Output device + SDL_Surface* m_surface; - // Memory-mapped IO Registers - std::tr1::shared_ptr m_reg; + // Shader program + GLint m_shaderProgram; + + Core::CallbackId m_mode0; + Core::CallbackId m_mode2; + Core::CallbackId m_mode3; + + 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; }; +} // namespace glBoy + #endif diff --git a/GameboyJoypad.cc b/GameboyJoypad.cc index e69de29..96cb681 100644 --- a/GameboyJoypad.cc +++ b/GameboyJoypad.cc @@ -0,0 +1,138 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" +#include "GameboyJoypad.hh" +#include "Core.hh" + +#include +#include +#include + +using namespace glBoy; + +// TODO implement interrupt! + + +GameboyJoypad::GameboyJoypad(Core& core) : + m_state(Joypad_Off) +{ + m_inputs[DOWN].sdlKey = SDLK_DOWN; + 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; + + 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) + { + case Joypad_Off: + result = 0xff; + break; + + case Joypad_Directional: + result = m_directionalState; + break; + + case Joypad_Button: + result = m_buttonState; + break; + }; + return result; +} + + +void +GameboyJoypad::write8(uint16_t, uint8_t value) +{ + switch (value) + { + case 0x20: + m_state = Joypad_Directional; + break; + case 0x10: + m_state = Joypad_Button; + break; + case 0x30: + m_state = Joypad_Off; + break; + default: + assert(false); + } +} + +void +GameboyJoypad::pollInput(Core::CallbackId) +{ + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + switch(event.type) + { + case SDL_KEYUP: + case SDL_KEYDOWN: + { + 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; +} + diff --git a/GameboyJoypad.hh b/GameboyJoypad.hh index e69de29..8fafbfd 100644 --- a/GameboyJoypad.hh +++ b/GameboyJoypad.hh @@ -0,0 +1,61 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_GAMEBOYJOYPAD_HH +#define GLBOY_GAMEBOYJOYPAD_HH + +#include "glBoy.hh" +#include "Core.hh" + +#include "MemoryMap.hh" + +#include + +namespace glBoy +{ + +class GameboyJoypad : public MemoryMap::Memory +{ +public: + GameboyJoypad(Core& core); + + void pollInput(Core::CallbackId id); + + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + +private: + enum State { Joypad_Off, Joypad_Directional, Joypad_Button }; + State m_state; + + struct Input + { + Input() : sdlKey(0), depressed(false) {} + int sdlKey; + 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; +}; + +} // namespace glBoy + +#endif diff --git a/GameboySound.cc b/GameboySound.cc index e69de29..606db98 100644 --- a/GameboySound.cc +++ b/GameboySound.cc @@ -0,0 +1,492 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" +#include "GameboySound.hh" + +#include +#include +#include + +using namespace glBoy; + +enum { SAMPLE_RATE = 48000 }; + +extern "C" +{ + void mixaudio(void *userData, Uint8 *stream, int len) + { + GameboySound* ourObject(reinterpret_cast(userData)); + ourObject->mixSounds(stream, len); + } +} + +GameboySound::GameboySound(Core& core) +{ + extern void mixaudio(void *unused, Uint8 *stream, int len); + 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 + 0, + std::bind(&glBoy::GameboySound::periodicUpdate, this, _1) + ); +} + +uint8_t +GameboySound::read8(uint16_t address) +{ + uint8_t result(0); + + switch (address) + { + case (0x0): + result = m_channel1.sweep; + break; + case (0x1): + result = m_channel1.duty; + break; + case (0x2): + result = m_channel1.volume; + break; + case (0x3): + result = m_channel1.freqLow; + break; + case (0x4): + result = m_channel1.freqHigh; + break; + + case (0x6): + result = m_channel2.duty; + break; + case (0x7): + result = m_channel2.volume; + break; + case (0x8): + result = m_channel2.freqLow; + break; + case (0x9): + result = m_channel2.freqHigh; + break; + + case (0xA): + result = m_channel3.enabled; + break; + case (0xB): + result = m_channel3.length; + break; + case (0xC): + result = m_channel3.volume; + break; + case (0xD): + result = m_channel3.freqLow; + break; + case (0xE): + result = m_channel3.freqHigh; + break; + + case (0x10): + result = m_channel4.length; + break; + case (0x11): + result = m_channel4.volume; + break; + case (0x12): + result = m_channel4.poly; + break; + case (0x13): + result = m_channel4.counter; + break; + // TODO REMOVE THIS AND IMPLEMENT EVERYTHING. + }; + + if (address >= 0x20 && address < 0x30) + { + result = m_channel3.waveTable[address - 0x20]; + } + return result; +} + +void +GameboySound::write8(uint16_t address, uint8_t value) +{ + switch (address) + { + case (0x0): + m_channel1.sweep = value; + m_channel1.sweepDelay = 0; + m_channel1.sweepCount = 0; + break; + case (0x1): + m_channel1.duty = value; + break; + case (0x2): + m_channel1.volume = value; + break; + case (0x3): + m_channel1.freqLow = value; + break; + case (0x4): + m_channel1.freqHigh = value; + + if (value & 0x80) + { + m_channel1.reset(); + } + break; + + case (0x6): + m_channel2.duty = value; + break; + case (0x7): + m_channel2.volume = value; + break; + case (0x8): + m_channel2.freqLow = value; + break; + case (0x9): + m_channel2.freqHigh = value; + if (value & 0x80) + { + m_channel2.reset(); + } + break; + + case (0xA): + m_channel3.enabled = value; + break; + case (0xB): + m_channel3.length= value; + break; + case (0xC): + m_channel3.volume = value; + break; + case (0xD): + m_channel3.freqLow = value; + break; + case (0xE): + m_channel3.freqHigh = value; + if (value & 0x80) + { + m_channel3.reset(); + } + break; + + + case (0x10): + m_channel4.length= value; + break; + case (0x11): + m_channel4.volume = value; + break; + case (0x12): + m_channel4.poly= value; + break; + case (0x13): + m_channel4.counter = value; + if (value & 0x80) + { + m_channel4.reset(); + } + break; + + // TODO REMOVE THIS AND IMPLEMENT EVERYTHING. + }; + + if (address >= 0x20 && address < 0x30) + { + m_channel3.waveTable[address - 0x20] = value; + } +} + +void GameboySound::periodicUpdate(Core::CallbackId) +{ + prepChannel(m_channel1); + prepChannel(m_channel2); + prepChannel3(); + prepChannel4(); +} + +void +GameboySound::mixSounds(Uint8 *stream, int len) +{ + for (int i = 0; i < len / 2; ++i) + { + 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; + } + +} + +int16_t +GameboySound::sample(SquareWave& channel) +{ + int16_t sample(0); + + if (channel.currentVolume && channel.remainingLength > 0) + { + int pos = channel.squareStep / (channel.samplesPerDutyBit); + + sample = channel.currentVolume * channel.currentVolume; + sample = sample * sample; + if (!((channel.dutyMask >> (7 - pos)) & 1)) + { + sample = -sample; + } + + ++channel.squareStep; + if (channel.squareStep > (channel.samplesPerDutyBit * 8)) + { + channel.squareStep = 0; + } + } + return sample; +} + +void +GameboySound::prepChannel(SquareWave& channel) +{ + uint16_t freqCode(channel.freqHigh & 0x7); + freqCode <<= 8; + freqCode |= channel.freqLow; + + double freq = 131072 / (2048.0 - freqCode); + + if (channel.sweep & 0x3) + { + if (channel.sweepDelay >= (channel.sweep & 0x3)) + { + channel.sweepDelay = 0; + ++channel.sweepCount; + } + else + { + ++channel.sweepDelay; + } + + double freqMod = freq / (1 << channel.sweepCount); + + if (channel.sweep & 0x8) + { + freq -= freqMod; + } + else + { + freq += freqMod; + } + } + + if (channel.volume & 0x3) + { + // volumeDelay in 1/64 of a sec. x 2, as this callback occurs every + // 1/128sec. + if (channel.volumeDelay >= ((channel.volume & 0x3) * 2)) + { + channel.volumeDelay = 0; + if (channel.volume & 0x8) + { + if (channel.currentVolume < 0xf) + { + ++channel.currentVolume; + } + } + else + { + if (channel.currentVolume != 0) + { + --channel.currentVolume; + } + + } + } + else + { + ++channel.volumeDelay; + } + } + + if (channel.freqHigh & 0x40) + { + if (channel.remainingLength > 0) --channel.remainingLength; + } + + channel.samplesPerDutyBit = (SAMPLE_RATE/freq) / 8; +} + +int16_t +GameboySound::sampleChannel4() +{ + int16_t sample(0); + + if (m_channel4.currentVolume && m_channel4.remainingLength > 0) + { + sample = m_channel4.currentVolume * m_channel4.currentVolume; + sample = sample * sample; + if (!m_channel4.high) + { + sample = -sample; + } + + ++m_channel4.currentCounter; + if (m_channel4.currentCounter > m_channel4.samplesPerStep) + { + m_channel4.high = rand() & 0x1; + } + } + return sample; +} + + +void +GameboySound::prepChannel4() +{ + Noise& channel(m_channel4); + + if (channel.volume & 0x3) + { + // volumeDelay in 1/64 of a sec. x 2, as this callback occurs every + // 1/128sec. + if (channel.volumeDelay >= ((channel.volume & 0x3) * 2)) + { + channel.volumeDelay = 0; + if (channel.volume & 0x8) + { + if (channel.currentVolume < 0xf) + { + ++channel.currentVolume; + } + } + else + { + if (channel.currentVolume != 0) + { + --channel.currentVolume; + } + + } + } + else + { + ++channel.volumeDelay; + } + } + + if (channel.counter & 0x40) + { + if (channel.remainingLength > 0) --channel.remainingLength; + } + + double ratio = channel.poly & 0x3; + if (ratio == 0) ratio = 0.5; + double freq = 524288 / ratio / std::pow(2.0, (channel.poly >> 4) + 1); + channel.samplesPerStep = (SAMPLE_RATE/freq); +} + + +void +GameboySound::prepChannel3() +{ + uint16_t freqCode(m_channel3.freqHigh & 0x7); + freqCode <<= 8; + freqCode |= m_channel3.freqLow; + + double freq = 65536 / (2048.0 - freqCode); + + if (m_channel3.freqHigh & 0x40) + { + if (m_channel3.remainingLength > 0) --m_channel3.remainingLength; + } + + m_channel3.samplesPerNibble = (SAMPLE_RATE/freq) / 32; +} + +int16_t +GameboySound::sampleChannel3() +{ + int16_t sample(0); + + if (m_channel3.remainingLength > 0 && + (m_channel3.enabled & 0x80) + ) + { + int volume((m_channel3.volume >> 5) & 0x3); + + uint8_t nibble = m_channel3.waveTable[m_channel3.wavePos / 2]; + + if (m_channel3.wavePos & 0x1) + { + nibble &= 0xf; + } + else + { + nibble >>= 4; + } + + if (volume == 0) + { + nibble = 0; + } + else if (volume == 2) + { + nibble >>= 1; + } + else if (volume == 3) + { + nibble >>= 3; + } + + // Ok, nibble is now between 0 and 0xf. Map this to full range. + sample = (static_cast(nibble) - 8) * 4096; + + ++m_channel3.waveSamples; + if (m_channel3.waveSamples > m_channel3.samplesPerNibble * (m_channel3.wavePos + 1)) + { + m_channel3.wavePos++; + if (m_channel3.wavePos >= 32) + { + m_channel3.waveSamples = 0; + m_channel3.wavePos = 0; + } + } + + } + return sample; +} diff --git a/GameboySound.hh b/GameboySound.hh index e69de29..8a01d39 100644 --- a/GameboySound.hh +++ b/GameboySound.hh @@ -0,0 +1,190 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_GAMEBOYSOUND_HH +#define GLBOY_GAMEBOYSOUND_HH + +#include "glBoy.hh" +#include "Core.hh" + +#include "MemoryMap.hh" + +#include + +namespace glBoy +{ + +class GameboySound : public MemoryMap::Memory +{ +public: + 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); +private: + int16_t getSample(uint8_t volume); + + struct SquareWave + { + SquareWave() : sweep(0), duty(0), volume(0), freqLow(0), freqHigh(0) + { + reset(); + } + + void reset() + { + sweepDelay = 0; + sweepCount = 0; + volumeDelay = 0; + currentVolume = volume >> 4; + squareStep = 0; + + switch (duty >> 6) + { + case 0: dutyMask = 0x8; break; + case 1: dutyMask = 0xC0; break; + case 2: dutyMask = 0xF0; break; + case 3: dutyMask = 0xFC; break; + }; + + // x 2, since callback @ 128Hz, not 256Hz. + if (freqHigh & 0x40) + { + // Add 1, as we immediately decrement in prep method + // without actually playing anything. + remainingLength = ((64 - (duty & 0x3f)) / 2) + 1; + } + else + { + remainingLength = 255; + } + } + // Regs. + uint8_t sweep; + uint8_t duty; + uint8_t volume; + uint8_t freqLow; + uint8_t freqHigh; + + // Runtime + int sweepDelay; + uint32_t sweepCount; + + int volumeDelay; + int currentVolume; + + uint8_t dutyMask; + double samplesPerDutyBit; + int squareStep; + + int remainingLength; + }; + int16_t sample(SquareWave& channel); + void prepChannel(SquareWave& channel); + + SquareWave m_channel1; + SquareWave m_channel2; + + struct Noise + { + Noise() : length(0), volume(0), poly(0), counter(0) {reset(); } + uint8_t length; + uint8_t volume; + uint8_t poly; + uint8_t counter; + + int currentCounter; + int samplesPerStep; + int volumeDelay; + int currentVolume; + + int remainingLength; + + bool high; + + void reset() + { + volumeDelay = 0; + currentVolume = volume >> 4; + + currentCounter = 0; + + remainingLength = (64 - (length & 0x3f)) / 2; + + high = false; + } + }; + + Noise m_channel4; + int16_t sampleChannel4(); + void prepChannel4(); + + struct Wave + { + Wave() : enabled(0), length(0), volume(0), freqLow(0), freqHigh(0) + { + reset(); + } + + void reset() + { + wavePos = 0; + waveSamples = 0; + + // x 2, since callback @ 128Hz, not 256Hz. + if (freqHigh & 0x40) + { + // Add 1, as we immediately decrement in prep method + // without actually playing anything. + remainingLength = ((256 - length) / 2) + 1; + } + else + { + remainingLength = 255; + } + } + // Regs. + uint8_t enabled; + uint8_t length; + uint8_t volume; + uint8_t freqLow; + uint8_t freqHigh; + + uint8_t waveTable[16]; + + // Runtime + int wavePos; + int waveSamples; + int remainingLength; + float samplesPerNibble; + }; + + Wave m_channel3; + int16_t sampleChannel3(); + void prepChannel3(); + + uint8_t m_channelControl; + uint8_t m_outputTerminal; +}; + +} // namespace glBoy + +#endif diff --git a/GameboyTimer.cc b/GameboyTimer.cc index e69de29..889eb2a 100644 --- a/GameboyTimer.cc +++ b/GameboyTimer.cc @@ -0,0 +1,160 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" +#include "GameboyTimer.hh" +#include "Log.hh" + +#include +#include + +#include + +using namespace glBoy; + +GameboyTimer::GameboyTimer( + Core& core, + const std::function& setFrameSkip, + const std::function& clearFrameSkip + ) : + m_core(core), + m_setSkipFrame(setFrameSkip), + m_clearSkipFrame(clearFrameSkip) +{ + memset(m_reg, 0, 4); + + using namespace std::placeholders; + core.registerPeriodic( + 256, + 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; + m_clockSleepCounter = m_clockSleepDelay; + +/* + if (m_clockSleepDelay) + { + Log::Instance()(Log::DBG_WARNING) << + "Timer delay set to " << m_clockSleepDelay << + " nanoseconds. Enable HR timers for improved emulation" << std::endl; + } +*/ + + clock_gettime(CLOCK_REALTIME, &m_lastSlept); +} + +// Call this every 256 clock cycles. (16384Hz) + +void +GameboyTimer::tick(Core::CallbackId) +{ + ++m_reg[DIV]; + + if (m_reg[TAC] & 0x4) // Timer enabled + { + int counter(m_reg[TIMA]); + switch (m_reg[TAC] & 0x3) + { + case 0: // 4096Hz + if (m_reg[DIV] % 4 == 0) ++counter; + break; + case 1: // 268400Hz + counter += 16; + break; + case 2: // 65536Hz + counter += 4; + break; + case 3: // 16384Hz + ++counter; + break; + } + + if (counter > 255) + { + m_reg[TIMA] = m_reg[TMA]; + m_core.raiseInterrupt(0x50); + } + else + { + m_reg[TIMA] = counter; + } + } + + m_lastSlept.tv_nsec += 61035; + if (m_lastSlept.tv_nsec > 1e9) + { + m_lastSlept.tv_nsec -= 1e9; + m_lastSlept.tv_sec++; + } + + 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) + ) + ) + { + m_clearSkipFrame(); + clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &m_lastSlept, NULL); + } + else + { + m_setSkipFrame(); + } + m_clockSleepCounter = m_clockSleepDelay; + } + else + { + m_clockSleepCounter--; + } + +} + +uint8_t +GameboyTimer::read8(uint16_t address) +{ + return m_reg[address]; +} + +void +GameboyTimer::write8(uint16_t address, uint8_t value) +{ + if (!address) + { + // Writing any value to DIV resets it to zero. + m_reg[DIV] = 0; + } + else + { + m_reg[address] = value; + } +} + diff --git a/GameboyTimer.hh b/GameboyTimer.hh index e69de29..2d5df43 100644 --- a/GameboyTimer.hh +++ b/GameboyTimer.hh @@ -0,0 +1,64 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_GAMEBOYTIMER_HH +#define GLBOY_GAMEBOYTIMER_HH + +#include "glBoy.hh" +#include "Core.hh" + +#include "MemoryMap.hh" + +#include + +#include + +namespace glBoy +{ + +class GameboyTimer : public MemoryMap::Memory +{ +public: + GameboyTimer( + Core& core, + const std::function& setFrameSkip, + const std::function& clearFrameSkip + ); + + void tick(Core::CallbackId id); + + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + +private: + enum Reg { DIV = 0, TIMA, TMA, TAC }; + + Core& m_core; + std::function m_setSkipFrame; + std::function m_clearSkipFrame; + + uint8_t m_reg[4]; + + timespec m_lastSlept; + + int m_clockSleepDelay; + int m_clockSleepCounter; +}; + +} // namespace glBoy + +#endif diff --git a/InstructionSet.opcode b/InstructionSet.opcode index 10c0b76..e4acc1f 100755 --- a/InstructionSet.opcode +++ b/InstructionSet.opcode @@ -1,160 +1,1678 @@ -code gen expectations: -masks result in each instruction being unrolled and the mask name being replaced -with an actual register -operand = op8(); -operand="UPPERCASE" is 16bit operand. (first 8 bits = low bytes) N = op16() - - - - - - - - - + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + + + + + + + + + + + + -r = r2; + r = r2; -r = n; + r = n; -r = m_mem.read8(m_reg.HL); + r = m_mem.read8(m_reg.HL); - -r = m_mem.read8(m_reg.IX, d); + + r = m_mem.read8(m_reg.IX, d); - -r = m_mem.read8(m_reg.IY, d); + + r = m_mem.read8(m_reg.IY, d); -m_mem.write8(m_reg.HL, r); + m_mem.write8(m_reg.HL, r); - -m_mem.write8(m_reg.IX, d, r); + + m_mem.write8(m_reg.IX, d, r); - -m_mem.write8(m_reg.IY, d, r); + + m_mem.write8(m_reg.IY, d, r); -m_mem.write8(m_reg.HL, n); + m_mem.write8(m_reg.HL, n); - - -m_mem.write8(m_reg.IX, d, n); + + + m_mem.write8(m_reg.IX, d, n); - -m_mem.write8(m_reg.IY, d, n); + + m_mem.write8(m_reg.IY, d, n); -m_reg.A = m_mem.read8(m_reg.BC); + m_reg.A = m_mem.read8(m_reg.BC); -m_reg.A = m_mem.read8(m_reg.DE); + m_reg.A = m_mem.read8(m_reg.DE); - + -m_reg.A = m_mem.read8(N); + m_reg.A = m_mem.read8(N); -m_mem.write8(m_reg.BC, m_reg.A); + m_mem.write8(m_reg.BC, m_reg.A); -m_mem.write8(m_reg.DE, m_reg.A); + m_mem.write8(m_reg.DE, m_reg.A); - + -m_mem.write8(N, m_reg.A); + m_mem.write8(N, m_reg.A); -m_reg.F.set(m_reg.I, *this); -m_reg.A = m_reg.I; + m_reg.F.set(m_reg.I, *this); + m_reg.A = m_reg.I; -m_reg.F.set(m_reg.R, *this); -m_reg.A = m_reg.R; + m_reg.F.set(m_reg.R, *this); + m_reg.A = m_reg.R; -m_reg.I = m_reg.A; + m_reg.I = m_reg.A; -m_reg.R = m_reg.A; + m_reg.R = m_reg.A; + + + + + + + d = N; + + + + + m_reg.IX = N; + + + + + m_reg.IY = N; + + + + + m_reg.HL = m_mem.read16(N); + + + + + d = m_mem.read16(N); + + + + + m_reg.IX = m_mem.read16(N); + + + + + m_reg.IY = m_mem.read16(N); + + + + + m_mem.write16(N, m_reg.HL); + + + + + m_mem.write16(N, d); + + + + + m_mem.write16(N, m_reg.IX); + + + + + m_mem.write16(N, m_reg.IY); + + + + + + m_reg.SP = m_reg.HL; + + + + + m_reg.SP = m_reg.IX; + + + + + m_reg.SP = m_reg.IY; + + + + + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, q); + + + + + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.IX); + + + + + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.IY); + + + + + q = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + + + + + m_reg.IX = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + + + + + m_reg.IY = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2);; + + + + + + + + DWORD tmp(m_reg.DE); + m_reg.DE = m_reg.HL; + m_reg.HL = tmp; + + + + + DWORD tmp(m_reg.AF); + m_reg.AF = m_regDash.AF; + m_regDash.AF = tmp; + + + + + DWORD tmpBC(m_reg.BC); + DWORD tmpDE(m_reg.BC); + DWORD tmpHL(m_reg.BC); + m_reg.BC = m_regDash.BC; + m_reg.DE = m_regDash.DE; + m_reg.HL = m_regDash.HL; + m_regDash.BC = tmpBC; + m_regDash.DE = tmpDE; + m_regDash.HL = tmpHL; + + + + + DWORD tmp(m_reg.HL); + m_reg.HL = m_mem.read16(m_reg.SP); + m_mem.write16(m_reg.SP, tmp); + + + + + DWORD tmp(m_reg.IX); + m_reg.IX = m_mem.read16(m_reg.SP); + m_mem.write16(m_reg.SP, tmp); + + + + + DWORD tmp(m_reg.IY); + m_reg.IY = m_mem.read16(m_reg.SP); + m_mem.write16(m_reg.SP, tmp); + + + + + m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); + m_reg.DE.inc(); + m_reg.HL.inc(); + m_reg.BC.dec(); + m_reg.F.setCounter(m_reg.BC); + + + + + do + { + m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); + m_reg.DE.inc(); + m_reg.HL.inc(); + m_reg.BC.dec(); + + clock += 21; // Increment for each loop iteration + // TODO interrupt(); // Check for interrupts at each iteration + } while (m_reg.BC.host() != 0); + m_reg.F.setCounter(m_reg.BC); + clock += 16; // Increment again when leaving loop. + + + + + m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); + m_reg.DE.dec(); + m_reg.HL.dec(); + m_reg.BC.dec(); + m_reg.F.setCounter(m_reg.BC); + + + + + do + { + m_mem.write8(m_reg.DE, m_mem.read8(m_reg.HL)); + m_reg.DE.dec(); + m_reg.HL.dec(); + m_reg.BC.dec(); + + clock += 21; // Increment for each loop iteration + // TODO interrupt(); // Check for interrupts at each iteration + } while (m_reg.BC.host() != 0); + m_reg.F.setCounter(m_reg.BC); + clock += 16; // Increment again when leaving loop. + + + + + m_reg.BC.dec(); + m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC); + m_reg.HL.inc(); + + + + + bool equal; + do + { + m_reg.BC.dec(); + m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC); + equal = (m_reg.A == m_mem.read8(m_reg.HL)); + m_reg.HL.inc(); + + clock += 21; // Increment for each loop iteration + // TODO interrupt(); // Check for interrupts at each iteration + } while (m_reg.BC.host() != 0 && !equal); + clock += 16; // Increment again when leaving loop. + + + + + + m_reg.BC.dec(); + m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC); + m_reg.HL.dec(); + + + + + bool equal; + do + { + m_reg.BC.dec(); + m_reg.F.setSub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.BC); + equal = (m_reg.A == m_mem.read8(m_reg.HL)); + m_reg.HL.dec(); + + clock += 21; // Increment for each loop iteration + // TODO interrupt(); // Check for interrupts at each iteration + } while (m_reg.BC.host() != 0 && !equal); + clock += 16; // Increment again when leaving loop. + + + + + + + m_reg.A = ALU::add(m_reg.A, r, m_reg.F); + + + + + m_reg.A = ALU::add(m_reg.A, n, m_reg.F); + + + + + m_reg.A = ALU::add(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + m_reg.A = ALU::add(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + m_reg.A = ALU::add(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + m_reg.A = ALU::adc(m_reg.A, r, m_reg.F); + + + + + m_reg.A = ALU::adc(m_reg.A, n, m_reg.F); + + + + + m_reg.A = ALU::adc(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + m_reg.A = ALU::adc(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + m_reg.A = ALU::adc(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + m_reg.A = ALU::sub(m_reg.A, r, m_reg.F); + + + + + m_reg.A = ALU::sub(m_reg.A, n, m_reg.F); + + + + + m_reg.A = ALU::sub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + m_reg.A = ALU::sub(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + m_reg.A = ALU::sub(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + m_reg.A = ALU::sbc(m_reg.A, r, m_reg.F); + + + + + m_reg.A = ALU::sbc(m_reg.A, n, m_reg.F); + + + + + m_reg.A = ALU::sbc(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + m_reg.A = ALU::sbc(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + m_reg.A = ALU::sbc(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + m_reg.A = ALU::bitwiseAnd(m_reg.A, r, m_reg.F); + + + + + m_reg.A = ALU::bitwiseAnd(m_reg.A, n, m_reg.F); + + + + + m_reg.A = ALU::bitwiseAnd(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + m_reg.A = ALU::bitwiseAnd(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + m_reg.A = ALU::bitwiseAnd(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + m_reg.A = ALU::bitwiseOr(m_reg.A, r, m_reg.F); + + + + + m_reg.A = ALU::bitwiseOr(m_reg.A, n, m_reg.F); + + + + + m_reg.A = ALU::bitwiseOr(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + m_reg.A = ALU::bitwiseOr(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + m_reg.A = ALU::bitwiseOr(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + + m_reg.A = ALU::bitwiseXor(m_reg.A, r, m_reg.F); + + + + + m_reg.A = ALU::bitwiseXor(m_reg.A, n, m_reg.F); + + + + + m_reg.A = ALU::bitwiseXor(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + m_reg.A = ALU::bitwiseXor(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + m_reg.A = ALU::bitwiseXor(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + ALU::sub(m_reg.A, r, m_reg.F); + + + + + ALU::sub(m_reg.A, n, m_reg.F); + + + + + ALU::sub(m_reg.A, m_mem.read8(m_reg.HL), m_reg.F); + + + + + ALU::sub(m_reg.A, m_mem.read8(m_reg.IX, d), m_reg.F); + + + + + ALU::sub(m_reg.A, m_mem.read8(m_reg.IY, d), m_reg.F); + + + + + r = ALU::add(r, 1, m_reg.F); + + + + + m_mem.write8(m_reg.HL, ALU::add(m_mem.read8(m_reg.HL), 1, m_reg.F)); + + + + + m_mem.write8(m_reg.IX, d, ALU::add(m_mem.read8(m_reg.IX, d), 1, m_reg.F)); + + + + + m_mem.write8(m_reg.IY, d, ALU::add(m_mem.read8(m_reg.IY, d), 1, m_reg.F)); + + + + + r = ALU::sub(r, 1, m_reg.F); + + + + + m_mem.write8(m_reg.HL, ALU::sub(m_mem.read8(m_reg.HL), 1, m_reg.F)); + + + + + m_mem.write8(m_reg.IX, d, ALU::sub(m_mem.read8(m_reg.IX, d), 1, m_reg.F)); + + + + + m_mem.write8(m_reg.IY, d, ALU::sub(m_mem.read8(m_reg.IY, d), 1, m_reg.F)); + + + + + + + + m_reg.A = DAA(m_reg.A, m_reg.F); + + + + + m_reg.A = ~m_reg.A; + m_reg.F.H = 1; + m_reg.F.N = 1; + + + + + m_reg.A = ALU::sub(0, m_reg.A, m_reg.F); + + + + + m_reg.F.C = (m_reg.F.C) ? 0 : 1; + m_reg.F.N = 0; + + + + + m_reg.F.H = 0; + m_reg.F.N = 0; + m_reg.F.C = 1; + + + + + // Do nothing + + + + + halt(); + + + + + m_iff1 = 0; + m_iff2 = 0; + + + + + m_iff1 = 1; + m_iff2 = 1; + + + + + m_interruptMode = IM_0; + + + + + m_interruptMode = IM_1; + + + + + m_interruptMode = IM_2; + + + + + + + m_reg.HL = ALU::add(m_reg.HL, s, m_reg.F); + + + + + m_reg.HL = ALU::adc(m_reg.HL, s, m_reg.F); + + + + + m_reg.HL = ALU::sbc(m_reg.HL, s, m_reg.F); + + + + + m_reg.IX = ALU::add(m_reg.IX, p, m_reg.F); + + + + + m_reg.IY = ALU::add(m_reg.IY, R, m_reg.F); + + + + + s.inc(); + + + + + m_reg.IX.inc(); + + + + + m_reg.IY.inc(); + + + + + s.dec(); + + + + + m_reg.IX.dec(); + + + + + m_reg.IY.dec(); + + + + + + + m_reg.A = ALU::rotateLeft(m_reg.A, m_reg.F, false); + + + + + m_reg.A = ALU::rotateLeft(m_reg.A, m_reg.F, true); + + + + + m_reg.A = ALU::rotateRight(m_reg.A, m_reg.F, false); + + + + + m_reg.A = ALU::rotateRight(m_reg.A, m_reg.F, true); + + + + + r = ALU::rotateLeft(r, m_reg.F, false, true); + + + + + m_mem.write8( + m_reg.HL, + ALU::rotateLeft(m_mem.read8(m_reg.HL), m_reg.F, false, true) + ); + + + + + m_mem.write8( + m_reg.IX, + d, + ALU::rotateLeft(m_mem.read8(m_reg.IX, d), m_reg.F, false, true) + ); + + + + + m_mem.write8( + m_reg.IY, + d, + ALU::rotateLeft(m_mem.read8(m_reg.IY, d), m_reg.F, false, true) + ); + + + + + r = ALU::rotateLeft(r, m_reg.F, true, true); + + + + + m_mem.write8( + m_reg.HL, + ALU::rotateLeft(m_mem.read8(m_reg.HL), m_reg.F, true, true) + ); + + + + + m_mem.write8( + m_reg.IX, + d, + ALU::rotateLeft(m_mem.read8(m_reg.IX, d), m_reg.F, true, true) + ); + + + + + m_mem.write8( + m_reg.IY, + d, + ALU::rotateLeft(m_mem.read8(m_reg.IY, d), m_reg.F, true, true) + ); + + + + + r = ALU::rotateRight(r, m_reg.F, false, true); + + + + + m_mem.write8( + m_reg.HL, + ALU::rotateRight(m_mem.read8(m_reg.HL), m_reg.F, false, true) + ); + + + + + m_mem.write8( + m_reg.IX, + d, + ALU::rotateRight(m_mem.read8(m_reg.IX, d), m_reg.F, false, true) + ); + + + + + m_mem.write8( + m_reg.IY, + d, + ALU::rotateRight(m_mem.read8(m_reg.IY, d), m_reg.F, false, true) + ); + + + + + r = ALU::rotateRight(r, m_reg.F, true, true); + + + + + m_mem.write8( + m_reg.HL, + ALU::rotateRight(m_mem.read8(m_reg.HL), m_reg.F, true, true) + ); + + + + + m_mem.write8( + m_reg.IX, + d, + ALU::rotateRight(m_mem.read8(m_reg.IX, d), m_reg.F, true, true) + ); + + + + + m_mem.write8( + m_reg.IY, + d, + ALU::rotateRight(m_mem.read8(m_reg.IY, d), m_reg.F, true, true) + ); + + + + + r = ALU::shiftLeft(r, m_reg.F); + + + + + m_mem.write8( + m_reg.HL, + ALU::shiftLeft(m_mem.read8(m_reg.HL), m_reg.F) + ); + + + + + m_mem.write8( + m_reg.IX, + d, + ALU::shiftLeft(m_mem.read8(m_reg.IX, d), m_reg.F) + ); + + + + + m_mem.write8( + m_reg.IY, + d, + ALU::shiftLeft(m_mem.read8(m_reg.IY, d), m_reg.F) + ); + + + + + r = ALU::shiftRight(r, m_reg.F, true); + + + + + m_mem.write8( + m_reg.HL, + ALU::shiftRight(m_mem.read8(m_reg.HL), m_reg.F, true) + ); + + + + + m_mem.write8( + m_reg.IX, + d, + ALU::shiftRight(m_mem.read8(m_reg.IX, d), m_reg.F, true) + ); + + + + + m_mem.write8( + m_reg.IY, + d, + ALU::shiftRight(m_mem.read8(m_reg.IY, d), m_reg.F, true) + ); + + + + + r = ALU::shiftRight(r, m_reg.F, false); + + + + + m_mem.write8( + m_reg.HL, + ALU::shiftRight(m_mem.read8(m_reg.HL), m_reg.F, false) + ); + + + + + m_mem.write8( + m_reg.IX, + d, + ALU::shiftRight(m_mem.read8(m_reg.IX, d), m_reg.F, false) + ); + + + + + m_mem.write8( + m_reg.IY, + d, + ALU::shiftRight(m_mem.read8(m_reg.IY, d), m_reg.F, false) + ); + + + + + uint8_t m(m_mem.read8(m_reg.HL)); + ALU::RLD(m_reg.A, m, m_reg.F); + m_mem.write8(m_reg.HL, m); + + + + + uint8_t m(m_mem.read8(m_reg.HL)); + ALU::RRD(m_reg.A, m, m_reg.F); + m_mem.write8(m_reg.HL, m); + + + + + + + m_reg.F.Z = ((~r) >> b) & 0x1; + m_reg.F.H = 1; + m_reg.F.N = 0; + + + + + m_reg.F.Z = ((~m_mem.read8(m_reg.HL)) >> b) & 0x1; + m_reg.F.H = 1; + m_reg.F.N = 0; + + + + + m_reg.F.Z = ((~m_mem.read8(m_reg.IX, d)) >> b) & 0x1; + m_reg.F.H = 1; + m_reg.F.N = 0; + + + + + m_reg.F.Z = ((~m_mem.read8(m_reg.IY, d)) >> b) & 0x1; + m_reg.F.H = 1; + m_reg.F.N = 0; + + + + + r |= (0x1 << b); + + + + + uint8_t tmp(m_mem.read8(m_reg.HL)); + tmp |= (0x1 << b); + m_mem.write8(m_reg.HL, tmp); + + + + + uint8_t tmp(m_mem.read8(m_reg.IX, d)); + tmp |= (0x1 << b); + m_mem.write8(m_reg.IX, d, tmp); + + + + + uint8_t tmp(m_mem.read8(m_reg.IY, d)); + tmp |= (0x1 << b); + m_mem.write8(m_reg.IY, d, tmp); + + + + + r &= 0xFF ^ (0x1 << b); + + + + + uint8_t tmp(m_mem.read8(m_reg.HL)); + tmp &= 0xFF ^ (0x1 << b); + m_mem.write8(m_reg.HL, tmp); + + + + + uint8_t tmp(m_mem.read8(m_reg.IX, d)); + tmp &= 0xFF ^ (0x1 << b); + m_mem.write8(m_reg.IX, d, tmp); + + + + + uint8_t tmp(m_mem.read8(m_reg.IY, d)); + tmp &= 0xFF ^ (0x1 << b); + m_mem.write8(m_reg.IY, d, tmp); + + + + + + m_reg.PC = N; + + + + + switch (b) + { + case 0: if (!m_reg.F.Z) m_reg.PC = N; break; + case 1: if (m_reg.F.Z) m_reg.PC = N; break; + case 2: if (!m_reg.F.C) m_reg.PC = N; break; + case 3: if (m_reg.F.C) m_reg.PC = N; break; + case 4: if (!m_reg.F.P) m_reg.PC = N; break; + case 5: if (m_reg.F.P) m_reg.PC = N; break; + case 6: if (!m_reg.F.S) m_reg.PC = N; break; + case 7: if (m_reg.F.S) m_reg.PC = N; break; + } + + + + + uint16_t pc = m_reg.PC.host(); + pc = pc + e; + m_reg.PC = htoz(pc); + + + + + if (m_reg.F.C) + { + uint16_t pc = m_reg.PC.host(); + pc = pc + e; + m_reg.PC = htoz(pc); + + clock += 5; + } + + + + + if (!m_reg.F.C) + { + uint16_t pc = m_reg.PC.host(); + pc = pc + e; + m_reg.PC = htoz(pc); + + clock += 5; + } + + + + + if (m_reg.F.Z) + { + uint16_t pc = m_reg.PC.host(); + pc = pc + e; + m_reg.PC = htoz(pc); + + clock += 5; + } + + + + + if (!m_reg.F.Z) + { + uint16_t pc = m_reg.PC.host(); + pc = pc + e; + m_reg.PC = htoz(pc); + + clock += 5; + } + + + + + m_reg.PC = m_reg.HL; + + + + + m_reg.PC = m_reg.IX; + + + + + m_reg.PC = m_reg.IY; + + + + + --m_reg.B; + if (m_reg.B) + { + uint16_t pc = m_reg.PC.host(); + pc = pc + e; + m_reg.PC = htoz(pc); + + clock += 5; + } + + + + + + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + m_reg.PC = N; + + + + + bool jump(false); + switch (b) + { + case 0: if (!m_reg.F.Z) jump = true; break; + case 1: if (m_reg.F.Z) jump = true; break; + case 2: if (!m_reg.F.C) jump = true; break; + case 3: if (m_reg.F.C) jump = true; break; + case 4: if (!m_reg.F.P) jump = true; break; + case 5: if (m_reg.F.P) jump = true; break; + case 6: if (!m_reg.F.S) jump = true; break; + case 7: if (m_reg.F.S) jump = true; break; + } + + if (jump) + { + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + m_reg.PC = N; + clock += 7; + } + + + + + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + + + + + bool jump(false); + switch (b) + { + case 0: if (!m_reg.F.Z) jump = true; break; + case 1: if (m_reg.F.Z) jump = true; break; + case 2: if (!m_reg.F.C) jump = true; break; + case 3: if (m_reg.F.C) jump = true; break; + case 4: if (!m_reg.F.P) jump = true; break; + case 5: if (m_reg.F.P) jump = true; break; + case 6: if (!m_reg.F.S) jump = true; break; + case 7: if (m_reg.F.S) jump = true; break; + } + + if (jump) + { + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + clock += 6; + } + + + + + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + m_iff1 = 1; + m_iff2 = 1; + + + + + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + + m_iff1 = m_iff2; + + // TODO interruptComplete(); + + + + + uint8_t addr; + switch (b) + { + case 0: addr = 0; break; + case 1: addr = 0x08; break; + case 2: addr = 0x10; break; + case 3: addr = 0x18; break; + case 4: addr = 0x20; break; + case 5: addr = 0x28; break; + case 6: addr = 0x30; break; + case 7: addr = 0x38; break; + } + + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + m_reg.PC.h = 0; + m_reg.PC.l = addr; + + + + + + + + prefix = (prefix << 8) | opcode; + opcode = op8(); + goto *(OpcodeTableCB[opcode]); + + + + + prefix = (prefix << 8) | opcode; + opcode = op8(); + goto *(OpcodeTableDD[opcode]); + + + + + prefix = (prefix << 8) | opcode; + opcode = op8(); + goto *(OpcodeTableED[opcode]); + + + + + prefix = (prefix << 8) | opcode; + opcode = op8(); + goto *(OpcodeTableFD[opcode]); + + + + + prefix = (prefix << 8) | opcode; + doublePrefixOperand = op8(); + opcode = op8(); + goto *(OpcodeTableDDCB[opcode]); + + + + + prefix = (prefix << 8) | opcode; + doublePrefixOperand = op8(); + opcode = op8(); + goto *(OpcodeTableFDCB[opcode]); + + + + + + m_mem.write16(N, m_reg.SP); + + + + + m_mem.write8(m_reg.HL, m_reg.A); + m_reg.HL.inc(); + + + + + m_reg.A = m_mem.read8(m_reg.HL); + m_reg.HL.inc(); + + + + + m_mem.write8(m_reg.HL, m_reg.A); + m_reg.HL.dec(); + + + + + m_reg.A = m_mem.read8(m_reg.HL); + m_reg.HL.dec(); + + + + + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + m_iff1 = 1; + m_iff2 = 1; + + + + + halt(); + + + + + + m_reg.SP = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F); + + + + + m_mem.write8(N, m_reg.A); + + + + m_reg.HL = ALU::add(m_reg.SP, htoz(int16_t(d)), m_reg.F); + + + + + m_reg.A = m_mem.read8(N);; + + + + + if (m_reg.F.C) + { + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + clock += 6; + } + + + + + if (!m_reg.F.C) + { + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + clock += 6; + } + + + + + if (m_reg.F.Z) + { + m_reg.PC = N; + clock += 6; + } + + + + + if (m_reg.F.Z) + { + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + clock += 6; + } + + + + + if (!m_reg.F.Z) + { + m_reg.PC = m_mem.read16(m_reg.SP); + m_reg.SP.inc(2); + clock += 6; + } + + + + + if (m_reg.F.Z) + { + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + m_reg.PC = N; + clock += 7; + } + + + + + if (!m_reg.F.Z) + { + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + m_reg.PC = N; + clock += 7; + } + + + + + if (m_reg.F.C) + { + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + m_reg.PC = N; + clock += 7; + } + + + + + if (!m_reg.F.C) + { + m_reg.SP.dec(2); + m_mem.write16(m_reg.SP, m_reg.PC); + m_reg.PC = N; + clock += 7; + } + + + + + if (!m_reg.F.Z) + { + m_reg.PC = N; + clock += 6; + } + + + + + if (!m_reg.F.C) + { + m_reg.PC = N; + clock += 6; + } + + + + + if (m_reg.F.C) + { + m_reg.PC = N; + clock += 6; + } + + + + + uint8_t tmp(r); + r <<= 4; + r |= (tmp >> 4); + + m_reg.F.Z = r == 0; + m_reg.F.N = 0; + m_reg.F.H = 0; + m_reg.F.C = 0; + + + + + + m_mem.write8(htoz(0xFF00 + n), m_reg.A); + + + + + m_mem.write8(htoz(0xFF00 + m_reg.C), m_reg.A); + + + + + + m_reg.A = m_mem.read8(htoz(0xFF00 + n)); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Log.cc b/Log.cc index e69de29..b77d0ca 100644 --- a/Log.cc +++ b/Log.cc @@ -0,0 +1,72 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "Log.hh" + +#include +#include +#include + +using namespace glBoy; + +Log& +Log::Instance() +{ + static Log s_instance; + return s_instance; +} + +Log::Log() : + m_levelMask(7), + m_nullStream(&m_nullBuf) +{ + char* env(getenv("GLBOY_DEBUG_LEVEL")); + if (env) + { + std::stringstream stream; + stream << env; + + // If this conversion fails, level mask remains 0, which is ok. + stream >> m_levelMask; + } +} + +void +Log::setDebugLevel(uint32_t levelMask) +{ + m_levelMask = levelMask; +} + +bool +Log::isDebug(DebugLevel level) const +{ + return (m_levelMask & level) != 0; +} + +std::ostream& +Log::operator()(DebugLevel level) +{ + if (isDebug(level)) + { + return std::cout; + } + else + { + return m_nullStream; + } +} + diff --git a/Log.hh b/Log.hh index e69de29..1afdb65 100644 --- a/Log.hh +++ b/Log.hh @@ -0,0 +1,62 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_LOG_HH +#define GLBOY_LOG_HH + +#include "glBoy.hh" + +#include +#include +#include + +namespace glBoy +{ + class Log + { + public: + + enum DebugLevel + { + DBG_STAT = 1, DBG_ERROR = 2, DBG_WARNING = 4, DBG_MEMORY = 8, DBG_TRACE = 16 + }; + static Log& Instance(); + + void setDebugLevel(uint32_t levelMask); + + bool isDebug(DebugLevel level) const; + std::ostream& operator()(DebugLevel level); + + private: + Log(); + + Log(const Log&); + Log& operator=(const Log&); + + uint32_t m_levelMask; + + class NullBuf : public std::streambuf + { + public: + }; + + NullBuf m_nullBuf; + std::ostream m_nullStream; + }; + +} // namespace glBoy +#endif diff --git a/Main.cc b/Main.cc index e69de29..b4db93c 100644 --- a/Main.cc +++ b/Main.cc @@ -0,0 +1,283 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + + +#include "glBoy.hh" +#include "Core.hh" +#include "GameboyCart.hh" +#include "GameboyGraphics.hh" +#include "GameboyJoypad.hh" +#include "GameboySound.hh" +#include "GameboyTimer.hh" +#include "RAM.hh" +#include "ROM.hh" +#include "Log.hh" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace +{ + SDL_Surface* + initOpenGL(double xScale, double yScale, bool fullscreen) + { + glBoy::Log::Instance()(glBoy::Log::DBG_STAT) << "Enabling OpenGL" << std::endl; + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + unsigned int options(SDL_OPENGL); + if (fullscreen) + { + options |= SDL_FULLSCREEN; + } + + SDL_Surface* surface( + SDL_SetVideoMode(160*xScale, 144*yScale, 32, options) + ); + + glEnable(GL_TEXTURE_2D); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0f, 160, 144, 0.0f, -1.0f, 1.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + return surface; + } + + void usage(const std::string& exe) + { + std::cout << + "Usage: " << exe << " " + "[--scale ratio] [--width w] [--height h] \n" + //"\t\t [--resample-method={bilinear|nearest} \n" + //"\t\t[--scaling-method={hq4x|scale2x|none}] " + "rom.gb" << std::endl << + std::endl << + "\t--scale\t(Default=4.0)" << std::endl << + "\t\tMultiplier applied to the display output dimensions." << std::endl << + "\t--width" << std::endl << + "\t\tSet the display output dimensions directly" << std::endl << + "\t--height" << std::endl << + "\t\tSet the display output dimensions directly" << std::endl; + /*"\t--scaling-method\n\t\t\t(Default=\"hq4x\")" << std::endl << + "\t\tSets the upscaling interpolation method." << std::endl << + "\t--resample-method\n\t\t\t(Default=\"bilinear\")" << std::endl << + "\t\tSets the resampling method." << std::endl;*/ + } +} + +int main(int argc, char** argv) +{ + static struct option longOptions[] = + { + {"video", 1, NULL, 'v'}, + {"scale", 1, NULL, 's'}, + {"width", 1, NULL, 'w'}, + {"height", 1, NULL, 'h'}, + //{"resample-method", 1, NULL, 'r'}, + //{"scaling-method", 1, NULL, 'm'}, + {NULL, 0, NULL, 0} + }; + + std::string filename("tetris.gb"); + glBoy::GameboyGraphics::PixelScaler scaler(glBoy::GameboyGraphics::Scaler_hq4x); + glBoy::GameboyGraphics::ResampleMethod resampler(glBoy::GameboyGraphics::Resample_Bilinear); + bool fullscreen(false); + double scaleX(4.0); + double scaleY(4.0); + + int optionIndex(0); + int c; + while ( + (c = getopt_long(argc, argv, "s:w:h:", &longOptions[0], &optionIndex)) + != -1) + { + switch (c) + { + case 'h': + { + int height(160); + std::stringstream convert(optarg); + convert >> height; + if (convert && height > 14 && height < 1440) + { + scaleY = height / 144.0; + } + }; break; + case 'r': + { + if (std::string(optarg) == "bilinear") + { + resampler = glBoy::GameboyGraphics::Resample_Bilinear; + } + else + { + resampler = glBoy::GameboyGraphics::Resample_Nearest; + } + }; break; + case 's': + { + double scale(1); + std::stringstream convert(optarg); + convert >> scale; + if (convert && scale > 0.1 && scale < 10) + { + scaleX = scale; + scaleY = scale; + } + }; break; + case 'm': + { + if (std::string(optarg) == "hq4x") + { + scaler = glBoy::GameboyGraphics::Scaler_hq4x; + } + else if (std::string(optarg) == "scale2x") + { + scaler = glBoy::GameboyGraphics::Scaler_Scale2x; + } + else + { + scaler = glBoy::GameboyGraphics::Scaler_None; + } + }; break; + case 'w': + { + int width(160); + std::stringstream convert(optarg); + convert >> width; + if (convert && width > 16 && width < 1600) + { + scaleX = width / 160.0; + } + }; break; + + case '?': + usage(argv[0]); + exit(1); + break; + + default: + abort(); + }; + } + + if (optind < argc) + { + filename = argv[optind++]; + } + + glBoy::Core core; + glBoy::MemoryMap& map(core.getMemoryMap()); + + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO); + + SDL_Surface* surface(initOpenGL(scaleX, scaleY, fullscreen)); + + glBoy::GameboyGraphics graphics(core, surface, scaler, resampler); + + // Map the cart memory. + int fd(open(filename.c_str(), O_RDONLY)); + assert(fd >= 0); + + struct stat buf; + if (fstat(fd, &buf)) + { + assert(!"Failed fstat"); + } + uint8_t* cart = new uint8_t[buf.st_size]; + read(fd, cart, buf.st_size); + close(fd); + + glBoy::GameboyCart tmp(core, cart, buf.st_size); + + +/* + int biosFd(open("DMG_ROM.bin", O_RDONLY)); + uint8_t bios[256]; + read(biosFd, bios, 256); + std::shared_ptr biosMem(new glBoy::ROM(bios, 256)); + map.map(0, 256, biosMem); +*/ + + // Map the cart mem. + // Map the standard internal RAM, plus shadow. + { + std::shared_ptr mem(new glBoy::RAM(8192)); + map.map(0xC000, 8192, mem); + map.map(0xE000, 7680, mem); + } + // Map the "zero-page" high-speed internal ram. + // Most interaction between GB hardware and the program occurs here. + { + std::shared_ptr mem(new glBoy::RAM(128)); + map.map(0xFF80, 128, mem); + } + + // Map the Interrupt flags. Currently handled in Core.cc + { + std::shared_ptr mem(new glBoy::RAM(1)); + mem->write8(0, 0); + map.map(0xFF0F, 1, mem); + } + + // Map other devices + std::shared_ptr joypad(new glBoy::GameboyJoypad(core)); + map.map(0xFF00, 1, joypad); + std::shared_ptr timer( + new glBoy::GameboyTimer( + core, + std::bind(&glBoy::GameboyGraphics::setFrameSkip, &graphics), + std::bind(&glBoy::GameboyGraphics::clearFrameSkip, &graphics) + ) + ); + map.map(0xFF04, 4, timer); + std::shared_ptr sound(new glBoy::GameboySound(core)); + map.map(0xFF10, 48, sound); + + // Cart starts from offset 0x100 with a NOP; JP + core.getRegisters().PC = glBoy::htoz(0x100); + core.getRegisters().SP = glBoy::htoz(0xFFFE); + core.getRegisters().AF = glBoy::htoz(0x01B0); + core.getRegisters().BC = glBoy::htoz(0x0013); + core.getRegisters().DE = glBoy::htoz(0x00D8); + core.getRegisters().HL = glBoy::htoz(0x014D); + + core.run(); + + std::cerr << "Done" << std::endl; + + SDL_FreeSurface(surface); + SDL_Quit(); +} + diff --git a/Makefile b/Makefile index f897cc6..37af1ed 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,70 @@ +# Copyright (C) 2011 Michael McMaster +# +# This file is part of glBoy. +# +# glBoy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# glBoy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with glBoy. If not, see . -mm80: InstructionSet_Opcodes.cc ALU.cc Core.cc Memory.cc Registers.cc - g++ -std=c++0x -I. -o $@ $^ +VERSION=$(shell cat VERSION) + +all: glBoy + +OBJS = \ + ALU.o \ + Core.o \ + GameboyCart.o \ + GameboyGraphics.o \ + GameboyJoypad.o \ + GameboySound.o \ + GameboyTimer.o \ + hq4x.o \ + Log.o \ + Main.o \ + MemoryMap.o \ + RAM.o \ + Registers.o \ + ROM.o \ + +DEFINES= +# -DGLBOY_DEBUG + +CPPFLAGS=-DHOST_LITTLE_ENDIAN -I. $(DEFINES) +CXXFLAGS=-g -O3 -march=native -W -Wall -Werror -std=c++0x + +LDFLAGS=-lSDL -lGL -lGLU -lglut + +glBoy: InstructionSet_Opcodes.cc $(OBJS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJS) InstructionSet_Opcodes.cc: InstructionSet.opcode makeOpcodes.pl perl ./makeOpcodes.pl -test: InstructionSet_Opcodes.cc Test.cc - g++ -std=c++0x -I. -o $@ Test.cc +Core.o: InstructionSet_Opcodes.cc + +test: InstructionSet_Opcodes.cc Test.cc ALU.cc + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ Test.cc ALU.cc + +package: + git commit -a + git tag -a -f $(VERSION) + mkdir /tmp/glBoy-$(VERSION) + -rm /tmp/glBoy-$(VERSION)/* + cp *.cc *.hh *.opcode *.pl Makefile COPYING README VERSION /tmp/glBoy-$(VERSION) + tar cvf glBoy-$(VERSION).tar -C /tmp glBoy-$(VERSION) + +clean: + -rm -f *.o + -rm -f glBoy + -rm -f InstructionSet_Opcodes.cc + -rm -f InstructionSet_Tables.cc + -rm -f gmon.out diff --git a/MemoryMap.cc b/MemoryMap.cc index e54bd2d..6264756 100644 --- a/MemoryMap.cc +++ b/MemoryMap.cc @@ -1,66 +1,208 @@ -/** - * Flat memory-model emulation. - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . -#include "bits/Z80.hh" -#include "ByteOrder.hh" -#include "Memory.hh" +#include "glBoy.hh" +#include "MemoryMap.hh" +#include "Log.hh" +#include "util.hh" -#include +#include +#include -using namespace Z80; +using namespace glBoy; -Memory::Memory(address_t initialSize) +namespace { - m_mem.resize(initialSize); + class NullMemory : public MemoryMap::Memory + { + public: + virtual uint8_t read8(uint16_t) { return 0; } + virtual void write8(uint16_t, uint8_t) { } + }; + + struct NullDeleter + { + void operator()(MemoryMap::Memory*) {} + }; } -u8_t Memory::read8(address_t address) +MemoryMap::MemoryMap() { - return m_mem[address]; } -u8_t Memory::read8(address_t address, offset_t offset) +void +MemoryMap::map( + uint16_t base, + uint16_t size, + const std::shared_ptr& mem + ) { - return m_mem[address + offset]; + MappedArea mapping(base, size, mem); + + MapType::iterator it( + std::lower_bound(m_map.begin(), m_map.end(), mapping) + ); + m_map.insert(it, mapping); + + Log::Instance()(Log::DBG_MEMORY) << + "Registering " << glBoy::hex(base) << + " for " << glBoy::hex(size) << " bytes."<< std::endl; } -void Memory::write8(address_t address, u8_t value) +void +MemoryMap::remove( + uint16_t base + ) { - m_mem[address] = value; + MapType::iterator it(m_map.begin()); + for (; it != m_map.end() && it->base != base ; ++it) ; + + if (it != m_map.end()) + { + Log::Instance()(Log::DBG_MEMORY) << + "Removing " << glBoy::hex(it->base) << + " for " << glBoy::hex(it->size) << " bytes."<< std::endl; + + m_map.erase(it); + } } -void Memory::write8(address_t address, offset_t offset, u8_t value) + +const MemoryMap::MappedArea& +MemoryMap::get(uint16_t address) const { - m_mem[address + offset] = value; + +/* + MappedArea test(address, 0, std::shared_ptr()); + MapType::const_iterator it( + std::lower_bound(m_map.begin(), m_map.end(), test) + ); + + + if (it != m_map.end() && it->base == address) + { + return *it; + } + else if (it != m_map.begin()) + { + --it; + if (address < it->base + static_cast(it->size)) + { + return *it; + } + } +*/ + + + for ( + MapType::const_iterator it(m_map.begin()); + it != m_map.end(); + ++it + ) + { + if ((address >= it->base) && + (address < it->base + static_cast(it->size)) + ) + { + return *it; + } + } + + Log::Instance()(Log::DBG_MEMORY) << + "Returning NULL memory for address " << glBoy::hex(address) << std::endl; + + static NullMemory s_null; + static MappedArea s_nullArea( + 0, 0, std::shared_ptr(&s_null, NullDeleter()) + ); + return s_nullArea; } -u16_t Memory::read16(address_t address) +uint8_t +MemoryMap::read8(DWORD address, int8_t offset) { - return Z80ToHost(&m_mem[address]); + uint16_t offsetAddress(address.host() + offset); + const MappedArea& mapping(get(offsetAddress)); + return mapping.memory->read8(offsetAddress - mapping.base); } -u16_t Memory::read16(address_t address, offset_t offset) +DWORD +MemoryMap::read16(DWORD address, int8_t offset) { - return Z80ToHost(&m_mem[address + offset]); + uint16_t offsetAddress(address.host() + offset); + + const MappedArea& loMap(get(offsetAddress)); + uint8_t lo(loMap.memory->read8(offsetAddress - loMap.base)); + + ++offsetAddress; + const MappedArea& hiMap(get(offsetAddress)); + uint16_t hi(hiMap.memory->read8(offsetAddress - hiMap.base)); + + return htoz((hi << 8) | lo); } -void Memory::write16(address_t address, u16_t value) +void +MemoryMap::write8(DWORD address, uint8_t value) { - hostToZ80(value, &m_mem[address]); + write8(address, 0, value); } -void Memory::write16(address_t address, offset_t offset, u16_t value) +void +MemoryMap::write8(DWORD address, int8_t offset, uint8_t value) { - hostToZ80(value, &m_mem[address + offset]); + uint16_t offsetAddress(address.host() + offset); + const MappedArea& mapping(get(offsetAddress)); + mapping.memory->write8(offsetAddress - mapping.base, value); } +void +MemoryMap::write16(DWORD address, DWORD value) +{ + write16(address, 0, value); +} + +void +MemoryMap::write16(DWORD address, int8_t offset, DWORD value) +{ + uint16_t offsetAddress(address.host() + offset); + const MappedArea& loMap(get(offsetAddress)); + loMap.memory->write8(offsetAddress - loMap.base, value.l); + + ++offsetAddress; + const MappedArea& hiMap(get(offsetAddress)); + hiMap.memory->write8(offsetAddress - hiMap.base, value.h); +} + +void +MemoryMap::copy(uint8_t* dest, uint16_t srcAddress, int bytes) +{ + const MappedArea& srcMap(get(srcAddress)); + + assert((srcAddress + bytes) <= (srcMap.base + srcMap.size)); + + srcMap.memory->copy(dest, srcAddress - srcMap.base, bytes); +} -void Memory::copy(address_t src, address_t dst, address_t count) +void +MemoryMap::Memory::copy(uint8_t* dest, uint16_t address, int bytes) { - memcpy(&m_mem[dst], &m_mem[src], count); + for (int i = 0; i < bytes; ++i) + { + dest[i] = read8(address + i); + } } diff --git a/MemoryMap.hh b/MemoryMap.hh index e54bd2d..7e7eb8c 100644 --- a/MemoryMap.hh +++ b/MemoryMap.hh @@ -1,66 +1,87 @@ -/** - * Flat memory-model emulation. - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . -#include "bits/Z80.hh" -#include "ByteOrder.hh" -#include "Memory.hh" +#ifndef GLBOY_MEMORYMAP_HH +#define GLBOY_MEMORYMAP_HH -#include +#include "glBoy.hh" +#include "DWORD.hh" -using namespace Z80; - -Memory::Memory(address_t initialSize) +#include +#include +#include +namespace glBoy { - m_mem.resize(initialSize); -} -u8_t Memory::read8(address_t address) +class MemoryMap { - return m_mem[address]; -} +public: + class Memory + { + public: + virtual ~Memory() {} -u8_t Memory::read8(address_t address, offset_t offset) -{ - return m_mem[address + offset]; -} + virtual uint8_t read8(uint16_t address) = 0; + virtual void write8(uint16_t address, uint8_t value) = 0; + virtual void copy(uint8_t* dest, uint16_t address, int bytes); + }; -void Memory::write8(address_t address, u8_t value) -{ - m_mem[address] = value; -} + MemoryMap(); -void Memory::write8(address_t address, offset_t offset, u8_t value) -{ - m_mem[address + offset] = value; -} + void map( + uint16_t base, + uint16_t size, + const std::shared_ptr& mem + ); -u16_t Memory::read16(address_t address) -{ - return Z80ToHost(&m_mem[address]); -} + void remove(uint16_t base); -u16_t Memory::read16(address_t address, offset_t offset) -{ - return Z80ToHost(&m_mem[address + offset]); -} + uint8_t read8(DWORD address, int8_t offset = 0); + void write8(DWORD address, uint8_t value); + void write8(DWORD address, int8_t offset, uint8_t value); -void Memory::write16(address_t address, u16_t value) -{ - hostToZ80(value, &m_mem[address]); -} + DWORD read16(DWORD address, int8_t offset = 0); + void write16(DWORD address, DWORD value); + void write16(DWORD address, int8_t offset, DWORD value); -void Memory::write16(address_t address, offset_t offset, u16_t value) -{ - hostToZ80(value, &m_mem[address + offset]); -} + void copy(uint8_t* dest, uint16_t srcAddress, int bytes); +private: + struct MappedArea + { + MappedArea( + uint16_t _base, uint16_t _size, std::shared_ptr _mem + ) : + base(_base), size(_size), memory(_mem) {} -void Memory::copy(address_t src, address_t dst, address_t count) -{ - memcpy(&m_mem[dst], &m_mem[src], count); -} + uint16_t base; + uint16_t size; + std::shared_ptr memory; + + bool operator<(const MappedArea& b) const { return base < b.base; } + }; + const MappedArea& get(uint16_t address) const; + + typedef std::vector MapType; + MapType m_map; + //std::set m_map; +}; + +} // namespace glBoy + +#endif diff --git a/RAM.cc b/RAM.cc index dfd546a..400eb33 100644 --- a/RAM.cc +++ b/RAM.cc @@ -1,55 +1,48 @@ -/** - * Flat memory-model emulation. - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ - -#include "mm80.hh" -#include "Memory.hh" - -using namespace mm80; - -Memory::Memory(address_t initialSize) +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" +#include "RAM.hh" + +#include + +using namespace glBoy; + +RAM::RAM(uint16_t size) { - m_mem.resize(initialSize); + m_mem.resize(size); } uint8_t -Memory::read8(DWORD address, int8_t offset) +RAM::read8(uint16_t address) { - return m_mem[address.host() + offset]; -} - -DWORD -Memory::read16(DWORD address, int8_t offset) -{ - return CreateDWORD(&m_mem[address.host() + offset]); + return m_mem[address]; } void -Memory::write8(DWORD address, uint8_t value) +RAM::write8(uint16_t address, uint8_t value) { - m_mem[address.host()] = value; + m_mem[address] = value; } -void -Memory::write8(DWORD address, int8_t offset, uint8_t value) -{ - m_mem[address.host() + offset] = value; -} - -void -Memory::write16(DWORD address, DWORD value) -{ - m_mem[address.host()] = value.l; - m_mem[address.host() + 1] = value.h; -} void -Memory::write16(DWORD address, int8_t offset, DWORD value) +RAM::copy(uint8_t* dest, uint16_t address, int bytes) { - m_mem[address.host() + offset] = value.l; - m_mem[address.host() + offset + 1] = value.h; + memcpy(dest, &m_mem[0] + address, bytes); } diff --git a/RAM.hh b/RAM.hh index 8f82353..b7ae42f 100644 --- a/RAM.hh +++ b/RAM.hh @@ -1,56 +1,44 @@ -/** - * Flat memory-model emulation. - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_RAM_HH +#define GLBOY_RAM_HH + +#include "glBoy.hh" +#include "MemoryMap.hh" -#ifndef MM80_MEMORYMAP_HH -#define MM80_MEMORYMAP_HH - -#include "mm80.hh" -#include "DWORD.hh" - -#include #include -namespace mm80 +namespace glBoy { -class MemoryMap +class RAM : public MemoryMap::Memory { public: - class Memory - { - public: - virtual ~Memory; - - uint8_t read8(DWORD address) = 0; - void write8(DWORD address, uint8_t value) = 0; - }; - - Memory(); + RAM(uint16_t size); + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + virtual void copy(uint8_t* dest, uint16_t address, int bytes); - void map( - address_t base, - address_t size, - const std::tr1::shared_ptr& mem - ); - - uint8_t read8(DWORD address, int8_t offset = 0); - void write8(DWORD address, uint8_t value); - void write8(DWORD address, int8_t offset, uint8_t value); - - DWORD read16(DWORD address, int8_t offset = 0); - void write16(DWORD address, DWORD value); - void write16(DWORD address, int8_t offset, DWORD value); private: - - std::vector m_map; std::vector m_mem; }; -} // namespace mm80 +} // namespace glBoy #endif diff --git a/README b/README index 034c583..e33c6da 100644 --- a/README +++ b/README @@ -1,5 +1,28 @@ -Third attempt at a Z80 CPU emulator. -email@michaelmcmaster.name 2008 +glBoy +Original gameboy emulator. -Language choice: C++ +Usage: +Usage: ./glBoy [--scale ratio] [--width w] [--height h] rom.gb + + --scale (Default=4.0) + Multiplier applied to the display output dimensions. + --width + Set the display output dimensions directly + --height + Set the display output dimensions directly + + +Features + - MBC1 emulation + - Sound + - OpenGL hq4x scaling + +Known Issues + - The emulator crashes on some games due to incorrect z80 + emulation. + +Missing + - MBC2/3/4 emulation + - Saved games + - Serial I/O diff --git a/ROM.cc b/ROM.cc index 4c3f03d..23fd637 100644 --- a/ROM.cc +++ b/ROM.cc @@ -1,22 +1,53 @@ -#include "mm80.hh" -#include "RAM.hh" +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . -using namespace mm80; +#include "glBoy.hh" +#include "ROM.hh" +#include "Log.hh" -RAM::RAM(uint16_t size) +#include + +#include + +using namespace glBoy; + +ROM::ROM(const uint8_t* data, uint16_t size) : + m_mem(data, data + size) { - m_mem.resize(size); } uint8_t -RAM::read8(uint16_t address) +ROM::read8(uint16_t address) { return m_mem[address]; } void -RAM::write8(uint16_t address, uint8_t value) +ROM::write8(uint16_t address, uint8_t) +{ + //assert(false); + Log::Instance()(Log::DBG_MEMORY) << + "Attempt to write to read-only address " << address << + std::endl; +} + +void +ROM::copy(uint8_t* dest, uint16_t address, int bytes) { - m_mem[address] = value; + memcpy(dest, &m_mem[0] + address, bytes); } diff --git a/ROM.hh b/ROM.hh index 812bfc7..1e7172d 100644 --- a/ROM.hh +++ b/ROM.hh @@ -1,25 +1,43 @@ -#ifndef MM80_RAM_HH -#define MM80_RAM_HH +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . -#include "mm80.hh" +#ifndef GLBOY_ROM_HH +#define GLBOY_ROM_HH + +#include "glBoy.hh" #include "MemoryMap.hh" #include -namespace mm80 +namespace glBoy { class ROM : public MemoryMap::Memory { public: - RAM(uint16_t size); - virtual uint8_t read8(uint16_t address) = 0; - virtual void write8(uint16_t address, uint8_t value) = 0; + ROM(const uint8_t* data, uint16_t size); + virtual uint8_t read8(uint16_t address); + virtual void write8(uint16_t address, uint8_t value); + virtual void copy(uint8_t* dest, uint16_t address, int bytes); private: std::vector m_mem; }; -} // namespace mm80 +} // namespace glBoy #endif diff --git a/Registers.cc b/Registers.cc old mode 100755 new mode 100644 index 775d2af..1f83436 --- a/Registers.cc +++ b/Registers.cc @@ -1,50 +1,71 @@ -/** - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ -#include "bits/Z80.hh" +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "glBoy.hh" #include "Registers.hh" #include "Core.hh" -using namespace Z80; - -Flags::Flags() : - C(0), - N(0), - P(0), - U3(0), - H(0), - U5(0), - Z(0), - S(0) -{ - // do nothing -} +#include + +using namespace glBoy; -op8_t Flags::operator()(op8_t operand, const Core& context) +void +Flags::set(op8_t operand, const Core& context) { - S = operand < 0 ? 1 : 0; + S = static_cast(operand) < 0 ? 1 : 0; Z = operand == 0 ? 1 : 0; - U5 = operand & 0b00010000 >> 4; + U5 = (operand & 0b00010000) >> 4; H = 0; - U3 = operand & 0b00000100 >> 2; + U3 = (operand & 0b00000100) >> 2; P = context.getIFF2(); N = 0; // C is not affected - return operand; } -Registers::Registers() : - AF(0), - BC(0), - DE(0), - HL(0), - I(0), - R(0), - IX(0), - IY(0), - SP(0), - PC(0) +void +Flags::setCounter(DWORD BC) { + // S is not affected + // Z is not affected + U5 = (BC.host() & 0b00010000) >> 4; + H = 0; + U3 = (BC.host() & 0b00000100) >> 2; + P = BC.host() ? 1 : 0; + N = 0; + // C is not affected } + +void +Flags::setSub(op8_t A, op8_t B, DWORD BC) +{ + S = (A > B) ? 1 : 0; + Z = (A == B) ? 1 : 0; + U5 = (A & 0b00010000) >> 4; + H = (B & 0xF) > (A & 0xF); + U3 = (A & 0b00000100) >> 2; + P = BC.host() ? 1 : 0; + N = 1; + // C is not affected +} + +void +Registers::reset() +{ + memset(this, 0, sizeof(Registers)); +} + diff --git a/Registers.hh b/Registers.hh old mode 100755 new mode 100644 index 6decfff..4f460cb --- a/Registers.hh +++ b/Registers.hh @@ -1,21 +1,27 @@ -/** - * Emulated representation of the Z80 CPU registers. - * - * The Z80 register emulation allows efficient access to both the 8-bit , - * registers an the 16-bit register pairs. This is achieved by modifying the - * structure layout according to the endianess of the host. - * - * The Z80 cpu supports switching between multiple register sets. Each Register - * object only stores the details of a single register set. - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - */ -#pragma once - -#include "bits/Z80.hh" - -namespace Z80 +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_Registers_hh +#define GLBOY_Registers_hh + +#include "glBoy.hh" +#include "DWORD.hh" + +namespace glBoy { class Core; @@ -31,21 +37,35 @@ class Core; * Z Zero Flag * S Sign Flag. MSB of Flag register */ -struct Flags +struct __attribute__ ((__packed__)) Flags { - unsigned C:1; /// Carry. LSB of Flag register - unsigned N:1; /// Add/Subtract - unsigned P:1; /// (V) Parity/Overflow - unsigned U3:1; /// 3rd bit of last 8bit op that altered flags - unsigned H:1; /// Half-Carry (BCD) - unsigned U5:1; /// 5th bit of last 8bit op that altered flags - unsigned Z:1; /// Zero Flag - unsigned S:1; /// Sign Flag. MSB of Flag register + union + { + struct __attribute__ ((__packed__)) + { + unsigned C:1; /// Carry. LSB of Flag register + unsigned N:1; /// Add/Subtract + unsigned P:1; /// (V) Parity/Overflow + unsigned U3:1; /// 3rd bit of last 8bit op that altered flags + unsigned H:1; /// Half-Carry (BCD) + unsigned U5:1; /// 5th bit of last 8bit op that altered flags + unsigned Z:1; /// Zero Flag + unsigned S:1; /// Sign Flag. MSB of Flag register + }; + reg8_t byte; + }; - Flags(); + // No ctor allowed for POD. + void reset() { byte = 0; } // Update the flags based on an operands value (eg. for simple assignment) - op8_t operator()(op8_t operand, const Core& context); + void set(op8_t operand, const Core& context); + + // Update the flags when changing BC + void setCounter(DWORD BC); + + // Set from A - B + void setSub(op8_t A, op8_t B, DWORD BC); }; /** Z80 register access. @@ -56,54 +76,43 @@ struct Flags */ struct Registers { - Registers(); + // No ctor allowed for POD. + void reset(); - // Storage Order allows for 16 bit pairs to be accessed naturally. - // ie. AF, BC, DE, HL - // eg. AF = 0x1234, A will == 0x12. -#ifdef HOST_LITTLE_ENDIAN #define Z80_REG_STRUCT(h,l) \ struct \ { \ l; \ h; \ }; -#else -#define Z80_REG_STRUCT(h,l) \ - struct \ - { \ - h; \ - l; \ - }; -#endif // Note: The order of these unions/structs is important, as // offsets are taken into the Register struct - reg8_t[0] begin8; - reg16_t[0] begin16; + reg8_t begin8[0]; + DWORD begin16[0]; union { Z80_REG_STRUCT(reg8_t B, reg8_t C); - reg16_t BC; + DWORD BC; }; union { Z80_REG_STRUCT(reg8_t D, reg8_t E); - reg16_t DE; + DWORD DE; }; union { Z80_REG_STRUCT(reg8_t H, reg8_t L); - reg16_t HL; + DWORD HL; }; union { - Z80_REG_STRUCT(reg8_t A, Flags F,); - reg16_t AF; + Z80_REG_STRUCT(reg8_t A, Flags F); + DWORD AF; }; @@ -112,9 +121,13 @@ struct Registers // Special Purpose reg8_t I; // Interrupt Vector reg8_t R; // Memory Refresh - reg16_t IX; // Index Register - reg16_t IY; // Index Register - reg16_t SP; // Stack Pointer - reg16_t PC; // Program Counter + DWORD IX; // Index Register + DWORD IY; // Index Register + DWORD SP; // Stack Pointer + DWORD PC; // Program Counter }; +} // namespace glBoy + +#endif + diff --git a/Resample.cc b/Resample.cc deleted file mode 100644 index e69de29..0000000 diff --git a/Test.cc b/Test.cc index e69de29..458b8a2 100644 --- a/Test.cc +++ b/Test.cc @@ -0,0 +1,275 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#include "ALU.hh" +#include "DAA.hh" + +#include "stddef.h" + +#include +#include +#include + +using namespace glBoy; + +void add(uint8_t a, uint8_t b) +{ + Flags f; + f.reset(); + uint8_t result(ALU::add(a, b, f)); + + uint16_t testFlags; + uint8_t testResult; + __asm__ ( + "movb %2, %%al\n\t" + "addb %3, %%al\n\t" + "pushf\n\t" + "mov %%al, %1\n\t" + "pop %%ax\n\t" + "movw %%ax, %0\n\t" + : "=g" (testFlags), "=g" (testResult) + : "g" (a), "g" (b) + : "eax", "cc" + ); + + assert(result == testResult); + assert(f.S == (testFlags & 0x80) >> 7); + assert(f.Z == (testFlags & 0x40) >> 6); + assert(f.H == (testFlags & 0x10) >> 4); + assert(f.P == (testFlags & 0x800) >> 11); // 0x4 for parity + assert(f.C == (testFlags & 0x1)); +} + +void sub(uint8_t a, uint8_t b) +{ + Flags f; + f.reset(); + uint8_t result(ALU::sub(a, b, f)); + + uint16_t testFlags; + uint8_t testResult; + __asm__ ( + "movb %2, %%al\n\t" + "subb %3, %%al\n\t" + "pushf\n\t" + "mov %%al, %1\n\t" + "pop %%ax\n\t" + "movw %%ax, %0\n\t" + : "=g" (testFlags), "=g" (testResult) + : "g" (a), "g" (b) + : "eax", "cc" + ); + + assert(result == testResult); + assert(f.S == (testFlags & 0x80) >> 7); + assert(f.Z == (testFlags & 0x40) >> 6); + assert(f.H == (testFlags & 0x10) >> 4); + assert(f.P == (testFlags & 0x800) >> 11); // 0x4 for parity + assert(f.C == (testFlags & 0x1)); +} + +void bitwiseAnd(uint8_t a, uint8_t b) +{ + Flags f; + f.reset(); + uint8_t result(ALU::bitwiseAnd(a, b, f)); + + uint16_t testFlags; + uint8_t testResult; + __asm__ ( + "movb %2, %%al\n\t" + "andb %3, %%al\n\t" + "pushf\n\t" + "mov %%al, %1\n\t" + "pop %%ax\n\t" + "movw %%ax, %0\n\t" + : "=g" (testFlags), "=g" (testResult) + : "g" (a), "g" (b) + : "eax", "cc" + ); + + assert(result == testResult); + assert(f.S == (testFlags & 0x80) >> 7); + assert(f.Z == (testFlags & 0x40) >> 6); + //assert(f.H == (testFlags & 0x10) >> 4); + // H is always set for AND for Z80, but not X86 + assert(f.H); + assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow + assert(f.C == (testFlags & 0x1)); +} + +void bitwiseOr(uint8_t a, uint8_t b) +{ + Flags f; + f.reset(); + uint8_t result(ALU::bitwiseOr(a, b, f)); + + uint16_t testFlags; + uint8_t testResult; + __asm__ __volatile__ ( + "movb %2, %%al\n\t" + "orb %3, %%al\n\t" + "pushf\n\t" + "mov %%al, %1\n\t" + "pop %%ax\n\t" + "movw %%ax, %0\n\t" + : "=g" (testFlags), "=g" (testResult) + : "g" (a), "g" (b) + : "eax", "cc" + ); + + assert(result == testResult); + assert(f.S == (testFlags & 0x80) >> 7); + assert(f.Z == (testFlags & 0x40) >> 6); + assert(f.H == (testFlags & 0x10) >> 4); + assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow + assert(f.C == (testFlags & 0x1)); +} + +void bitwiseXor(uint8_t a, uint8_t b) +{ + Flags f; + f.reset(); + uint8_t result(ALU::bitwiseXor(a, b, f)); + + uint16_t testFlags; + uint8_t testResult; + __asm__ __volatile__( + "movb %2, %%al\n\t" + "xorb %3, %%al\n\t" + "pushf\n\t" + "mov %%al, %1\n\t" + "pop %%ax\n\t" + "movw %%ax, %0\n\t" + : "=g" (testFlags), "=g" (testResult) + : "g" (a), "g" (b) + : "eax", "cc" + ); + + assert(result == testResult); + assert(f.S == (testFlags & 0x80) >> 7); + assert(f.Z == (testFlags & 0x40) >> 6); + assert(f.H == (testFlags & 0x10) >> 4); + assert(f.P == (testFlags & 0x04) >> 2); // 0x800 for overflow + assert(f.C == (testFlags & 0x1)); +} + +uint8_t daa(uint8_t a, uint8_t b) +{ + Flags f; + f.reset(); + uint8_t tmp(ALU::add(a, b, f)); + + return DAA(tmp, f); + +} + +uint8_t daaSub(uint8_t a, uint8_t b) +{ + Flags f; + f.reset(); + uint8_t tmp(ALU::sub(a, b, f)); + + return DAA(tmp, f); + +} + +int main() +{ + // Ensure packed representations are the expected size, + // and usable within union types. + assert(sizeof(DWORD) == 2); + assert(sizeof(Flags) == 1); + + // Ensure 16bit registers are aligned to 16-bit boundaries. + assert(offsetof(Registers, BC) == 0); + assert(offsetof(Registers, AF) == 6); + + add(0, 0); + add(1, 1); + add(-1, 1); + add(-128, 127); + add(127, 1); + add(-128, -1); + add(5, 5); + add(15, 1); + add(15, 128); + + Flags f; + f.reset(); + assert(ALU::add(htoz(0x1000), htoz(0x5555), f).host() == 0x6555); + assert(!f.C); + assert(ALU::add(htoz(0x1000), htoz(0xF555), f).host() == 0x0555); + assert(f.C); + + + sub(0, 0); + sub(1, 1); + sub(-1, 1); + sub(-128, 127); + sub(127, 1); + sub(-128, -1); + sub(5, 5); + sub(15, 1); + sub(15, 128); + sub(0, 0xb); + + bitwiseAnd(0, 0); + bitwiseAnd(1, 1); + bitwiseAnd(-1, 1); + bitwiseAnd(-128, 127); + bitwiseAnd(127, 1); + bitwiseAnd(-128, -1); + bitwiseAnd(5, 5); + bitwiseAnd(15, 1); + bitwiseAnd(15, 128); + + bitwiseOr(0, 0); + bitwiseOr(1, 1); + bitwiseOr(-1, 1); + bitwiseOr(-128, 127); + bitwiseOr(127, 1); + bitwiseOr(-128, -1); + bitwiseOr(5, 5); + bitwiseOr(15, 1); + bitwiseOr(15, 128); + + bitwiseXor(0, 0); + bitwiseXor(1, 1); + bitwiseXor(-1, 1); + bitwiseXor(-128, 127); + bitwiseXor(127, 1); + bitwiseXor(-128, -1); + bitwiseXor(5, 5); + bitwiseXor(15, 1); + bitwiseXor(15, 128); + + assert(daa(0x01, 0x01) == 0x02); + assert(daa(0x01, 0x09) == 0x10); + assert(daa(0x01, 0x10) == 0x11); + assert(daa(0x09, 0x09) == 0x18); + assert(daa(0x50, 0x49) == 0x99); + assert(daa(0x99, 0x0) == 0x99); + assert(daa(0x27, 0x27) == 0x54); + assert(daa(0x90, 0x10) == 0x0); // Carry from 0x100 + assert(daa(0x99, 0x99) == 0x98); // Carry from 0x198 + + assert(daaSub(0x10, 0x01) == 0x09); + assert(daaSub(0x10, 0x20) == 0x90); + + std::cout << "Tests complete" << std::endl; +} diff --git a/glBoy.hh b/glBoy.hh index 0733195..0fb885c 100644 --- a/glBoy.hh +++ b/glBoy.hh @@ -1,20 +1,28 @@ -/** - * Z80 CPU emulator - * - * Authors: Michael McMaster - * Copyright: Michael McMaster - * - * Description goes here - */ -#pragma once +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_HH +#define GLBOY_HH #include +#include -namespace Z80 +namespace glBoy { - typedef uint8_t u8_t; - typedef uint16_t u16_t; - typedef uint8_t op8_t; typedef uint8_t op16_t; @@ -22,14 +30,17 @@ namespace Z80 typedef uint16_t reg16_t; typedef uint16_t address_t; - typedef int16_t offset_t; typedef bool bit; - typedef uint64_t cycle_t; + typedef int64_t cycle_t; #if !(defined(HOST_LITTLE_ENDIAN) || defined(HOST_BIG_ENDIAN)) - #warn Assuming little-endian + #warning Assuming little-endian #define HOST_LITTLE_ENDIAN #endif + } + +#endif + diff --git a/hq4x.cc b/hq4x.cc index 6c145d2..d5521ad 100644 --- a/hq4x.cc +++ b/hq4x.cc @@ -1,76 +1,309 @@ -//hq4x filter demo program +// hq4x OpenGL Shader //---------------------------------------------------------- -//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) - +// Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) +// Copyright 2011 Michael McMaster +// //This program is free software; you can redistribute it and/or -//modify it under the terms of the GNU Lesser General Public +//modify it under the terms of the GNU General Public //License as published by the Free Software Foundation; either -//version 2.1 of the License, or (at your option) any later version. +//version 3 of the License, or (at your option) any later version. // //This program 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 //Lesser General Public License for more details. // -//You should have received a copy of the GNU Lesser General Public +//You should have received a copy of the GNU General Public //License along with this program; if not, write to the Free Software //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//---------------------------------------------------------- +// +// Changes from original code: +// - Converted to GLSL, inspired by the XNA port of hqNx by +// Phill Djonov (http://github.com/pdjonov/hqnx/wiki) +// - Applied clause 3 of LGPL 2.1 to alter license to GNU GPL 3. Updated +// License notice at top of file as required by clause 3. +#define GL_GLEXT_PROTOTYPES -// Converted to grayscale 2010 by Michael McMaster (Michael.McMaster@gmail.com) -// Then converted to generate a lookup table, inspired by -// Phill Djonov XNA implementation (http://github.com/pdjonov/hqnx/wiki) -// -But, each 3x3 weight matrix is packed into a single 32bit RGBA value, -// instead of 2 RGBA's. +#include "hq4x.hh" -/* -Each weight = number between 0 and 8 inclusive. 4 bits. -Treated as weight/8f +#include +#include -So, for each input pixel, build a 3x3 matrix of surrounding -pixels. - 1 2 3 12bits - 4 5 6 12bits - 7 8 9 12bits - ------ - 36 bits. -hmm. Skip the 5th weight. The matrix has to Sum to 8, so we can -make up 1 missing value. - 1 2 3 12bits - 4 _ 6 8bits - 7 8 9 12bits - ------ - 32 bits TICK +#include +#include +#include +#include +#include +#include -We then produce a 4x4 matrix of output pixels. +using namespace glBoy; -Need 256 sets of 16 x 3x3 matrices or, +namespace +{ -1) 3x3 matrix fix in 32bits. So just need a 256 sets of 4x4 matrix. -4096 pixels. +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\ +"; -2) There are 4 choices (2^4 outcomes = 16) -Diff (w[2], w[6]) -Diff (w[6], w[8]) -Diff (w[8], w[4]) -Diff (w[4], w[2]) +const char* +s_FragmentSource = +"\ +// Enable bitwise operators\n\ +#version 130\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\ +"; -Layout of texture: -Y = pattern # (0 -> 255) -X = - Top 4 bits = diff bitmask - Lower 4 bits = output pixel # -*/ +void abortGLError() +{ + std::cerr << "Fatal OpenGL error in hq4x shader: " << + gluErrorString(glGetError()) << std::endl; + abort(); +} -#include "hq4x.hh" +void abortShaderError(GLint shader) +{ + std::cerr << "Fatal error using hq4x shader: " << + gluErrorString(glGetError()) << std::endl; + + char buffer[2048]; + int len; + glGetShaderInfoLog(shader, 2048, &len, &buffer[0]); + std::cerr << buffer << std::endl; + + abort(); +} + +void abortProgramError(GLint program) +{ + std::cerr << "Fatal error using hq4x program: " << + gluErrorString(glGetError()) << std::endl; + + char buffer[2048]; + int len; + glGetProgramInfoLog(program, 2048, &len, &buffer[0]); + std::cerr << buffer << std::endl; + + abort(); +} + +} // namespace -#include -#include -#include -#include -#include -enum Diff { Diff_2_6 = 1, Diff_6_8 = 2, Diff_8_4 = 4, Diff_4_2 = 8 }; +Hq4x::Hq4x(size_t textureWidth, size_t textureHeight) : + m_textureWidth(textureWidth), + m_textureHeight(textureHeight) +{ + m_vertexShader = glCreateShader(GL_VERTEX_SHADER); + m_fragShader = glCreateShader(GL_FRAGMENT_SHADER); + + if (!m_vertexShader || !m_fragShader) abortGLError(); + + glShaderSource(m_vertexShader, 1, &s_VectorSource, NULL); + glShaderSource(m_fragShader, 1, &s_FragmentSource, NULL); + + glCompileShader(m_vertexShader); + if (glGetError() != GL_NO_ERROR) abortShaderError(m_vertexShader); + glCompileShader(m_fragShader); + if (glGetError() != GL_NO_ERROR) abortShaderError(m_fragShader); + + m_program = glCreateProgram(); + if (!m_program) abortGLError(); + + glAttachShader(m_program, m_vertexShader); + if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); + glAttachShader(m_program, m_fragShader); + if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); + glLinkProgram(m_program); + if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); + + glGenTextures(1, &m_lookupTexture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_lookupTexture); + + // Only nearest neighbour will work + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Create the texture image + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGBA, + 48, + 4096, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + getHq4xLookupTable() + ); + glUseProgram(m_program); + if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_lookupTexture); + GLint tex = glGetUniformLocation(m_program, "mmLookup"); + glUniform1i(tex, 1); // Bind Texture 1 +} + +void +Hq4x::prepareShader(GLuint texture) +{ + glUseProgram(m_program); + if (glGetError() != GL_NO_ERROR) abortProgramError(m_program); + + glBindFragDataLocationEXT(m_program,0,"fragColor"); + + GLint size(glGetUniformLocation(m_program, "mmTextureSize")); + glUniform4f(size, m_textureWidth, m_textureHeight, 0, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + GLint tex(glGetUniformLocation(m_program, "mmTexture")); + glUniform1i(tex, 0); // Bind Texture 0 + +/* + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_lookupTexture); + tex = glGetUniformLocation(m_program, "mmLookup"); + glUniform1i(tex, 1); // Bind Texture 1 +*/ +} + +//-------------------------------------------------- +// Lookup Table Generation Below +//-------------------------------------------------- struct weight { @@ -82,47 +315,47 @@ struct weight inline void Interp1(uint32_t& c1, uint32_t& c2) { - c1 = 6; - c2 = 2; + c1 = 191; + c2 = 64; } inline void Interp2(uint32_t& c1, uint32_t& c2, uint32_t& c3) { - c1 = 4; - c2 = 2; - c3 = 2; + c1 = 127; + c2 = 64; + c3 = 64; } inline void Interp3(uint32_t& c1, uint32_t& c2) { - c1 = 7; - c2 = 1; + c1 = 223; + c2 = 32; } inline void Interp5(uint32_t& c1, uint32_t& c2) { - c1 = 4; - c2 = 4; + c1 = 127; + c2 = 128; } inline void Interp6(uint32_t& c1, uint32_t& c2, uint32_t& c3) { - c1 = 5; - c2 = 2; - c3 = 1; + c1 = 159; + c2 = 64; + c3 = 32; } inline void Interp7(uint32_t& c1, uint32_t& c2, uint32_t& c3) { - c1 = 6; - c2 = 1; - c3 = 1; + c1 = 191; + c2 = 32; + c3 = 32; } inline void Interp8(uint32_t& c1, uint32_t& c2) { - c1 = 5; - c2 = 3; + c1 = 159; + c2 = 96; } @@ -144,7 +377,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define p15 weights[3][2] #define p16 weights[3][3] -#define PIXEL00_0 p1.w5 = 8; +#define PIXEL00_0 p1.w5 = 255; #define PIXEL00_11 Interp1(p1.w5, p1.w4); #define PIXEL00_12 Interp1(p1.w5, p1.w2);; #define PIXEL00_20 Interp2(p1.w5, p1.w2, p1.w4); @@ -153,7 +386,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL00_81 Interp8(p1.w5, p1.w4); #define PIXEL00_82 Interp8(p1.w5, p1.w2); -#define PIXEL01_0 p2.w5 = 8; +#define PIXEL01_0 p2.w5 = 255; #define PIXEL01_10 Interp1(p2.w5, p2.w1); #define PIXEL01_12 Interp1(p2.w5, p2.w2); #define PIXEL01_14 Interp1(p2.w2, p2.w5); @@ -165,7 +398,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL01_82 Interp8(p2.w5, p2.w2); #define PIXEL01_83 Interp8(p2.w2, p2.w4); -#define PIXEL02_0 p3.w5 = 8; +#define PIXEL02_0 p3.w5 = 255; #define PIXEL02_10 Interp1(p3.w5, p3.w3); #define PIXEL02_11 Interp1(p3.w5, p3.w2); #define PIXEL02_13 Interp1(p3.w2, p3.w5); @@ -177,7 +410,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL02_81 Interp8(p3.w5, p3.w2); #define PIXEL02_83 Interp8(p3.w2, p3.w6); -#define PIXEL03_0 p4.w5 = 8; +#define PIXEL03_0 p4.w5 = 255; #define PIXEL03_11 Interp1(p4.w5, p4.w2); #define PIXEL03_12 Interp1(p4.w5, p4.w6); #define PIXEL03_20 Interp2(p4.w5, p4.w2, p4.w6); @@ -186,7 +419,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL03_81 Interp8(p4.w5, p4.w2); #define PIXEL03_82 Interp8(p4.w5, p4.w6); -#define PIXEL10_0 p5.w5 = 8; +#define PIXEL10_0 p5.w5 = 255; #define PIXEL10_10 Interp1(p5.w5, p5.w1); #define PIXEL10_11 Interp1(p5.w5, p5.w4); #define PIXEL10_13 Interp1(p5.w4, p5.w5); @@ -198,19 +431,19 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL10_81 Interp8(p5.w5, p5.w4); #define PIXEL10_83 Interp8(p5.w4, p5.w2); -#define PIXEL11_0 p6.w5 = 8; +#define PIXEL11_0 p6.w5 = 255; #define PIXEL11_30 Interp3(p6.w5, p6.w1); #define PIXEL11_31 Interp3(p6.w5, p6.w4); #define PIXEL11_32 Interp3(p6.w5, p6.w2); #define PIXEL11_70 Interp7(p6.w5, p6.w4, p6.w2); -#define PIXEL12_0 p7.w5 = 8; +#define PIXEL12_0 p7.w5 = 255; #define PIXEL12_30 Interp3(p7.w5, p7.w3); #define PIXEL12_31 Interp3(p7.w5, p7.w2); #define PIXEL12_32 Interp3(p7.w5, p7.w6); #define PIXEL12_70 Interp7(p7.w5, p7.w6, p7.w2); -#define PIXEL13_0 p8.w5 = 8; +#define PIXEL13_0 p8.w5 = 255; #define PIXEL13_10 Interp1(p8.w5, p8.w3); #define PIXEL13_12 Interp1(p8.w5, p8.w6); #define PIXEL13_14 Interp1(p8.w6, p8.w5); @@ -222,7 +455,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL13_82 Interp8(p8.w5, p8.w6); #define PIXEL13_83 Interp8(p8.w6, p8.w2); -#define PIXEL20_0 p9.w5 = 8; +#define PIXEL20_0 p9.w5 = 255; #define PIXEL20_10 Interp1(p9.w5, p9.w7); #define PIXEL20_12 Interp1(p9.w5, p9.w4); #define PIXEL20_14 Interp1(p9.w4, p9.w5); @@ -234,19 +467,19 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL20_82 Interp8(p9.w5, p9.w4); #define PIXEL20_83 Interp8(p9.w4, p9.w8); -#define PIXEL21_0 p10.w5 = 8; +#define PIXEL21_0 p10.w5 = 255; #define PIXEL21_30 Interp3(p10.w5, p10.w7); #define PIXEL21_31 Interp3(p10.w5, p10.w8); #define PIXEL21_32 Interp3(p10.w5, p10.w4); #define PIXEL21_70 Interp7(p10.w5, p10.w4, p10.w8); -#define PIXEL22_0 p11.w5 = 8; +#define PIXEL22_0 p11.w5 = 255; #define PIXEL22_30 Interp3(p11.w5, p11.w9); #define PIXEL22_31 Interp3(p11.w5, p11.w6); #define PIXEL22_32 Interp3(p11.w5, p11.w8); #define PIXEL22_70 Interp7(p11.w5, p11.w6, p11.w8); -#define PIXEL23_0 p12.w5 = 8; +#define PIXEL23_0 p12.w5 = 255; #define PIXEL23_10 Interp1(p12.w5, p12.w9); #define PIXEL23_11 Interp1(p12.w5, p12.w6); #define PIXEL23_13 Interp1(p12.w6, p12.w5); @@ -258,7 +491,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL23_81 Interp8(p12.w5, p12.w6); #define PIXEL23_83 Interp8(p12.w6, p12.w8); -#define PIXEL30_0 p13.w5 = 8; +#define PIXEL30_0 p13.w5 = 255; #define PIXEL30_11 Interp1(p13.w5, p13.w8); #define PIXEL30_12 Interp1(p13.w5, p13.w4); #define PIXEL30_20 Interp2(p13.w5, p13.w8, p13.w4); @@ -267,7 +500,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL30_81 Interp8(p13.w5, p13.w8); #define PIXEL30_82 Interp8(p13.w5, p13.w4); -#define PIXEL31_0 p14.w5 = 8; +#define PIXEL31_0 p14.w5 = 255; #define PIXEL31_10 Interp1(p14.w5, p14.w7); #define PIXEL31_11 Interp1(p14.w5, p14.w8); #define PIXEL31_13 Interp1(p14.w8, p14.w5); @@ -279,7 +512,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL31_81 Interp8(p14.w5, p14.w8); #define PIXEL31_83 Interp8(p14.w8, p14.w4); -#define PIXEL32_0 p15.w5 = 8; +#define PIXEL32_0 p15.w5 = 255; #define PIXEL32_10 Interp1(p15.w5, p15.w9); #define PIXEL32_12 Interp1(p15.w5, p15.w8); #define PIXEL32_14 Interp1(p15.w8, p15.w5); @@ -291,7 +524,7 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) #define PIXEL32_82 Interp8(p15.w5, p15.w8); #define PIXEL32_83 Interp8(p15.w8, p15.w6); -#define PIXEL33_0 p16.w5 = 8; +#define PIXEL33_0 p16.w5 = 255; #define PIXEL33_11 Interp1(p16.w5, p16.w6); #define PIXEL33_12 Interp1(p16.w5, p16.w8); #define PIXEL33_20 Interp2(p16.w5, p16.w8, p16.w6); @@ -302,7 +535,8 @@ inline void Interp8(uint32_t& c1, uint32_t& c2) -uint32_t* getHq4xLookupTable() +uint32_t* +Hq4x::getHq4xLookupTable() const { static uint32_t* s_table(0); @@ -312,12 +546,14 @@ uint32_t* getHq4xLookupTable() } else { + s_table = new uint32_t[4096*48]; + for (int pattern = 0; pattern < 256; ++pattern) { - weight weights[4][4]; - for (int diff = 0; diff < 16; ++diff) { + weight weights[4][4]; + switch (pattern) { case 0: @@ -5299,22 +5535,41 @@ uint32_t* getHq4xLookupTable() { weight w(weights[pixely][pixelx]); - uint32_t rgba = - (w.w1 << 28) | - (w.w2 << 24) | - (w.w3 << 20) | - (w.w4 << 16) | - (w.w6 << 12) | - (w.w7 << 8) | - (w.w8 << 4) | - w.w9; + assert(w.w1+w.w2+w.w3+w.w4+w.w5+w.w6+w.w7+w.w8+w.w9 == 255); + size_t index( + ((pattern << 4) | diff) * 48 + (pixely*4+pixelx)*3 + ); + +// TODO byteorder fixes here! + s_table[index] = + (w.w1 << 0) | + (w.w2 << 8) | + (w.w3 << 16); + + + s_table[index + 1] = + (w.w4 << 0) | + (w.w5 << 8) | + (w.w6 << 16); + + s_table[index + 2] = + (w.w7 << 0) | + (w.w8 << 8) | + (w.w9 << 16); + +//hmm, order appears to be ABGR +/* + s_table[index] = 0x0090FF10; + s_table[index+1] = 0x00; + s_table[index+2] = 0x00; +*/ } } } } } - return s_lookup; + return s_table; } diff --git a/hq4x.hh b/hq4x.hh index e69de29..1242141 100644 --- a/hq4x.hh +++ b/hq4x.hh @@ -0,0 +1,50 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_HQ4X_HH +#define GLBOY_HQ4X_HH + +#include + +#include + +namespace glBoy +{ + class Hq4x + { + public: + Hq4x(size_t textureWidth, size_t textureHeight); + + void prepareShader(GLuint texture); + + 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; + }; +} + +#endif + diff --git a/makeOpcodes.pl b/makeOpcodes.pl index e69de29..2c4c5be 100644 --- a/makeOpcodes.pl +++ b/makeOpcodes.pl @@ -0,0 +1,393 @@ +#!/usr/bin/perl -w +# Copyright (C) 2011 Michael McMaster +# +# This file is part of glBoy. +# +# glBoy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# glBoy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with glBoy. If not, see . + +use warnings; +use strict; +use Carp; + +use XML::LibXML::Reader; + +# < Ugly Globals > +my %RegMasks = (); +my %BitMasks = (); +my %Instructions = (); + +my $VARIANT = "gb80"; + +# + +my $reader = new XML::LibXML::Reader(location => "InstructionSet.opcode") + || die "Cannot read opcode file\n"; + +open (TABLES, '>InstructionSet_Tables.cc') || die "Could not write to file\n"; +open (CODE, '>InstructionSet_Opcodes.cc') || die "Could not write to file\n"; + +$reader->read; +$reader->name eq "opcodes" || die "Invalid xml\n"; + +my $node = $reader->read; +while ($node) +{ + if ($reader->nodeType == XML_READER_TYPE_ELEMENT) + { + # Convert subtree to a DOM object. + my $subtree = $reader->copyCurrentNode(1); + + if ($reader->name eq "registerMask") + { + processRegisterMask($subtree); + } + elsif ($reader->name eq "bitMask") + { + processBitMask($subtree); + } + elsif ($reader->name eq "instruction") + { + processInstruction($subtree) + } + else + { + # Ignore unknown XML + } + } + + $node = $reader->next(); +} + +outputInstructionTable(); + +close TABLES; +close CODE; + +sub outputInstructionTable +{ + foreach my $prefix (keys %Instructions) + { + my $outPrefix = "OpcodeTable$prefix"; + $outPrefix =~ s/0x//; + $outPrefix =~ s/default//; + print TABLES "static void* $outPrefix\[256\] =\n{\n"; + + my %opcodes = %{$Instructions{$prefix}}; + + my $count = 0; + while ($count < 256) + { + if ($Instructions{$prefix}{$count}) + { + print TABLES "&&$Instructions{$prefix}{$count}"; + } + else + { + print TABLES "&&BAD_OPCODE"; + } + + $count = $count + 1; + + if ($count != 256) + { + print TABLES ",\t"; + } + if ($count % 4 == 0) + { + print TABLES "\n"; + } + } + print TABLES "};\n\n"; + } +} + + +sub processRegisterMask +{ + my $node = shift; + + my $name = $node->findvalue('./@name') || die; + my $maskBits = $node->findvalue('./@maskBits') || die; + ($maskBits > 0 && $maskBits < 8) || die; + my $regBytes = $node->findvalue('./@regBytes') || die; + ($regBytes > 0 && $regBytes <= 2) || die; + + my $variant = $node->findvalue('./@variant'); + + if ($variant && $variant ne $VARIANT) + { + return; + } + + my $max = 1 << $maskBits; + my $count = 0; + + print TABLES "static size_t regMask_$name\[$max\] =\n{\n"; + + 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)"; + } + + $count = $count + 1; + } + print TABLES "\n};\n\n"; + + $RegMasks{$name}{'masks'} = \@masks; + $RegMasks{$name}{'maskBits'} = $maskBits; + $RegMasks{$name}{'regBytes'} = $regBytes; + +} + +sub processBitMask +{ + my $node = shift; + + my $name = $node->findvalue('./@name') || die; + my $maskBits = $node->findvalue('./@maskBits') || die; + ($maskBits > 0 && $maskBits <= 8) || die; + + my $max = 1 << $maskBits; + my $count = 0; + + my @masks = (); + + while ($count < $max) + { + my $mask = sprintf("%0$maskBits"."b", $count); + push @masks, $mask; + $count = $count + 1; + } + + $BitMasks{$name}{'masks'} = \@masks; + $BitMasks{$name}{'maskBits'} = $maskBits; +} + +sub expandMask +{ + my $mask = shift; + + my @result = (); + + if ($mask =~ /([a-zA-Z])/) + { + my $match = $1; + + 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); + } + + } + $mask =~ s/$match/_/; + } + else + { + my $pos = 0; + my $value = 0; + + if (length $mask != 8) { croak "Bad Mask $mask\n"; } + while ($pos < 8) + { + 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 + { + $seenChars{$outputName} = 1; + } + + push @dbgResult, $outputName; + + my $maskBits = $RegMasks{$char}{'maskBits'}; + my $shiftNum = 8 - ($bitPos + $RegMasks{$char}{'maskBits'}); + if ($RegMasks{$char}{'regBytes'} == 1) + { + print CODE "\tuint8_t& $outputName(m_reg.begin8[regMask_$char\[(opcode >> $shiftNum) & (uint8_t(-1) >> (8 - $maskBits)) \]\]);\n"; + } + 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; + } + return @dbgResult; +} + +sub processInstruction +{ + my $node = 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'); + my $variant = $node->findvalue('./@variant'); + + if ($variant && $variant ne $VARIANT) + { + return; + } + if ($VARIANT eq "gb80" && $prefix && $prefix ne "0xCB") + { + return; + } + + print CODE "// Instruction: $name\n"; + print CODE "// Clock: $clock\n"; + my $label = $name; + $label =~ tr/ ,'+()/______/; + print CODE "$label:\n"; + print CODE "{\n"; + + + # Note: May have both signed operand (index offset) and operand + if ($signedOperand && length $signedOperand == 1) + { + print CODE "\tint8_t $signedOperand(op8());\n"; + } + + if ($operand && length $operand == 1) + { + if (length($prefix) > 4) + { + print CODE "\t uint8_t& $operand(doublePrefixOperand);\n"; + } + elsif (uc($operand) eq $operand) + { + print CODE "\tDWORD $operand(op16());\n"; + } + else + { + print CODE "\tuint8_t $operand(op8());\n"; + } + } + + my @dbgRegs = createRegReference($mask); + + print CODE "#ifdef GLBOY_DEBUG\n"; + print CODE "std::ostream& trace(log(Log::DBG_TRACE));\n". + "trace << ". + "opcodePC << \": \";\n". + "if (prefix) trace << hex(prefix) << \" \";\n". + "trace << hex(opcode) << \" $name\" << ". + ($operand ? "\" ($operand=\" << $operand << \")\" << " : "") . + ($dbgRegs[0] ? "\" ($dbgRegs[0]=\" << $dbgRegs[0] << \")\" << " : "") . + "std::endl;\n"; + print CODE "#endif\n"; + + my $statements = $node->findvalue('./text()'); + chomp($statements); + print CODE $statements."\n"; + + print CODE "\tclock += $clock;\n"; + + print "Processing $name\n"; + my @opcodes = expandMask($mask); + + if (!$prefix || length $prefix == 0) + { + $prefix = "default"; + } + + foreach my $opcode (@opcodes) + { + if ($Instructions{$prefix}{$opcode}) + { + die "Duplicate opcode. Prefix = $prefix. Mask = $mask, $name.\n". + "Conflicting label = $Instructions{$prefix}{$opcode}\n"; + } + $Instructions{$prefix}{$opcode} = $label; + } + + print CODE "\n}\n"; + print CODE "goto END_INSTRUCTIONS;\n"; + print CODE "\n\n"; +} + diff --git a/shaders/scale2x.slf b/shaders/scale2x.slf index 7f4cb3c..8dcc8dc 100644 --- a/shaders/scale2x.slf +++ b/shaders/scale2x.slf @@ -1,23 +1,11 @@ -// http://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/loading.php +uniform sampler2D mmTexture; +uniform vec4 mmTextureSize; -#version 130 - -uniform sampler2D OGL2Texture; -uniform vec4 OGL2Size; - -/* -void main (void) -{ - // Every pixel becomes green - gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); -} -*/ - -in vec4 vTexCoord; // supplied from vertex processor -in vec4 B; -in vec4 D; -in vec4 F; -in vec4 H; +varying vec4 vTexCoord; // supplied from vertex processor +varying vec4 B; +varying vec4 D; +varying vec4 F; +varying vec4 H; void main(void) @@ -35,11 +23,14 @@ void main(void) vec4 E2; vec4 E3; - eColour = texture2DProj(OGL2Texture, vTexCoord); - bColour = texture2DProj(OGL2Texture, B); - dColour = texture2DProj(OGL2Texture, D); - fColour = texture2DProj(OGL2Texture, F); - hColour = texture2DProj(OGL2Texture, H); + vec4 outRow1; + vec4 outRow2; + + eColour = texture2DProj(mmTexture, vTexCoord); + bColour = texture2DProj(mmTexture, B); + dColour = texture2DProj(mmTexture, D); + fColour = texture2DProj(mmTexture, F); + hColour = texture2DProj(mmTexture, H); if (bColour != hColour && dColour != fColour) { E0 = dColour == bColour ? dColour : eColour; @@ -54,30 +45,10 @@ void main(void) } // Is this pixel E0,E1,E2 or E3, or somewhere in between ? - fragmentLocation = fract(vTexCoord * OGL2Size); - //fragmentLocation = fract(vTexCoord * texturesize(OGL2Texture)); + // Bi-linear interpolate (may not be exact x2 scale) + fragmentLocation = fract(vTexCoord * mmTextureSize); - // TODO interpolate - if (fragmentLocation.x < 0.5) - { - if (fragmentLocation.y < 0.5) - { - gl_FragColor = E0; - } - else - { - gl_FragColor = E2; - } - } - else - { - if (fragmentLocation.y < 0.5) - { - gl_FragColor = E1; - } - else - { - gl_FragColor = E3; - } - } + outRow1 = mix(E0, E1, fragmentLocation.x); + outRow2 = mix(E2, E3, fragmentLocation.x); + gl_FragColor = mix(outRow1, outRow2, fragmentLocation.y); } diff --git a/shaders/scale2x.slv b/shaders/scale2x.slv index 2297a09..a2f67d5 100644 --- a/shaders/scale2x.slv +++ b/shaders/scale2x.slv @@ -1,38 +1,10 @@ -// http://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/loading.php +uniform vec4 mmTextureSize; - -// Built-in vertex attributes (per vertex) -// gl_Vertex Position of current vertex (vec4) -// gl_Color Primary colour of vertex (vec4) -//gl_MultiTexCoord0 Texture Coordinate of texture unit 0 - -// Built-in variables (passed from vertex to fragment) -// Will be interpolated between vertex and fragment. - -// Scales output, so image only takes up top left of screen (1/4 of image); -/* -void main(void) -{ - vec4 a = gl_Vertex; - a.x = a.x * 2.0;//0.5; - a.y = a.y * 0.5; - - - gl_Position = gl_ModelViewProjectionMatrix * a; - // gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; normal code - -} -*/ - -#version 130 - -uniform vec4 OGL2Param; - -out vec4 vTexCoord; // Passed to fragment -out vec4 B; // Passed to fragment -out vec4 D; // Passed to fragment -out vec4 F; // Passed to fragment -out vec4 H; // Passed to fragment +varying vec4 vTexCoord; // Passed to fragment +varying vec4 B; // Passed to fragment +varying vec4 D; // Passed to fragment +varying vec4 F; // Passed to fragment +varying vec4 H; // Passed to fragment void main(void) @@ -40,8 +12,8 @@ void main(void) vec4 offsetx; vec4 offsety; - offsetx = vec4(OGL2Param.x, 0, 0, 0); - offsety = vec4(0, OGL2Param.y, 0, 0); + offsetx = vec4(1.0/mmTextureSize.x, 0, 0, 0); + offsety = vec4(0, 1.0/mmTextureSize.y, 0, 0); B = gl_MultiTexCoord0 - offsety; D = gl_MultiTexCoord0 - offsetx; diff --git a/util.hh b/util.hh index e69de29..40dc5ec 100644 --- a/util.hh +++ b/util.hh @@ -0,0 +1,63 @@ +// Copyright (C) 2011 Michael McMaster +// +// This file is part of glBoy. +// +// glBoy is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// glBoy is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with glBoy. If not, see . + +#ifndef GLBOY_UTIL_HH +#define GLBOY_UTIL_HH + +#include +namespace glBoy +{ + static char hexChars[] = + {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + + inline std::string hex(uint8_t val) + { + std::string result("0xFF"); + result[2] = hexChars[val >> 4]; + result[3] = hexChars[val & 0xf]; + return result; + } + + inline std::string hex(int8_t val) + { + std::string result; + + if (val >= 0) + { + result = "0x"; + } + else + { + result = "-0x"; + } + result += hexChars[val >> 4]; + result += hexChars[val & 0xf]; + return result; + } + + inline std::string hex(uint16_t val) + { + std::string result("0xFFFF"); + result[2] = hexChars[val >> 12]; + result[3] = hexChars[(val >> 8) & 0xf]; + result[4] = hexChars[(val >> 4) & 0xf]; + result[5] = hexChars[val & 0xf]; + return result; + } +} +#endif + -- 2.38.5