Created
April 19, 2015 21:03
-
-
Save feliwir/ab743c40b6a9122dee08 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <stdint.h> | |
#include <string> | |
#include <chrono> | |
#include <SFML/Window.hpp> | |
#include <SFML/Audio.hpp> | |
#include "flextGL.h" | |
#define CPU_FREQ 60 | |
class Chip8 | |
{ | |
private: | |
const GLchar* m_vs = | |
"#version 330\n" | |
"vec2 coords[4] = vec2[4]((0.0,0.0),(0.0,1.0),(1.0,0.0),(1.0,1.0));" | |
"out vec2 outCoord;" | |
"void main(void){" | |
"outCoord = coords[gl_VertexID];\n" | |
"gl_Position = vec4(coords[gl_VertexID],0,1);" | |
"}"; | |
const GLchar* m_fs = | |
"#version 330\n" | |
"uniform sampler2D sampler;" | |
"in vec2 coord;" | |
"out vec4 color;" | |
"void main(void){" | |
"color = texture(sampler, coord);" | |
"}"; | |
public: | |
Chip8(); | |
void EmulateCycle(); | |
bool LoadROM(const std::string& name); | |
bool Draw(); | |
void HandleKey(sf::Event::KeyEvent& key); | |
inline uint8_t* GetScreen() | |
{ | |
return m_gfx; | |
} | |
private: | |
uint16_t m_opcode; | |
uint8_t m_memory[4096]; | |
uint8_t m_reg[16]; | |
uint16_t m_pc; | |
uint16_t m_index; | |
uint8_t m_gfx[64*32]; | |
uint8_t m_delayTimer; | |
uint8_t m_soundTimer; | |
uint16_t m_stack[16]; | |
uint16_t m_sp; | |
uint8_t m_key[16]; | |
uint32_t m_freq; | |
std::chrono::time_point<std::chrono::high_resolution_clock> m_last; | |
std::chrono::microseconds m_cycleInterval; | |
sf::SoundBuffer m_soundwave; | |
sf::Sound m_beep; | |
GLuint m_tex; | |
GLuint m_program,m_vs_id,m_fs_id; | |
GLuint m_vao; | |
bool m_drawFlag; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "Chip8.hpp" | |
#include <algorithm> | |
#include <fstream> | |
#include <cstring> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <time.h> | |
#define _USE_MATH_DEFINES | |
#include <math.h> | |
#include <thread> | |
#include <string.h> | |
Chip8::Chip8() : m_opcode(0),m_pc(0x200),m_index(0),m_sp(0),m_soundTimer(0),m_delayTimer(0), m_freq(CPU_FREQ) | |
{ | |
flextInit(); | |
GLint status; | |
const uint8_t fontset[80] = | |
{ | |
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 | |
0x20, 0x60, 0x20, 0x20, 0x70, // 1 | |
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 | |
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 | |
0x90, 0x90, 0xF0, 0x10, 0x10, // 4 | |
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 | |
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 | |
0xF0, 0x10, 0x20, 0x40, 0x40, // 7 | |
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 | |
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 | |
0xF0, 0x90, 0xF0, 0x90, 0x90, // A | |
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B | |
0xF0, 0x80, 0x80, 0x80, 0xF0, // C | |
0xE0, 0x90, 0x90, 0x90, 0xE0, // D | |
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E | |
0xF0, 0x80, 0xF0, 0x80, 0x80 // F | |
}; | |
//set the screen to 0 | |
std::memset(m_gfx, 0, sizeof(m_gfx)); | |
//set the memory to 0 | |
std::memset(m_memory, 0, sizeof(m_memory)); | |
//set all registers to 0 | |
std::memset(m_reg, 0, sizeof(m_reg)); | |
//set all keys to 0 | |
std::memset(m_key, 0, sizeof(m_key)); | |
//copy the fontset into memory | |
std::copy(fontset,fontset+80,&m_memory[0]); | |
srand (time(NULL)); | |
//create our beep sound | |
float freq = 440.f; | |
float seconds = 0.05f; | |
uint32_t sample_rate = 22050; | |
uint32_t buf_size = seconds * sample_rate; | |
int16_t* soundwave = new int16_t[buf_size]; | |
for (int i = 0; i<buf_size; ++i) | |
{ | |
soundwave[i] = 32760 * sin((2.f*float(M_PI)*freq) / sample_rate * i); | |
} | |
m_soundwave.loadFromSamples(soundwave, buf_size, 1, sample_rate); | |
m_beep.setBuffer(m_soundwave); | |
m_last = std::chrono::high_resolution_clock::now(); | |
m_cycleInterval = std::chrono::microseconds((int)((1.0f / m_freq)*1000000)); | |
glGenTextures(1, &m_tex); | |
glBindTexture(GL_TEXTURE_2D, m_tex); | |
m_program = glCreateProgram(); | |
m_vs_id = glCreateShader(GL_VERTEX_SHADER); | |
m_fs_id = glCreateShader(GL_FRAGMENT_SHADER); | |
glShaderSource(m_vs_id, 1, &m_vs, NULL); | |
glCompileShader(m_vs_id); | |
glGetShaderiv(m_vs_id, GL_COMPILE_STATUS, &status); | |
if (status != GL_TRUE) | |
{ | |
char buffer[512]; | |
glGetShaderInfoLog(m_vs_id, 512, NULL, buffer); | |
printf(buffer); | |
} | |
glShaderSource(m_fs_id, 1, &m_fs, NULL); | |
glCompileShader(m_fs_id); | |
glGetShaderiv(m_fs_id, GL_COMPILE_STATUS, &status); | |
if (status != GL_TRUE) | |
{ | |
char buffer[512]; | |
glGetShaderInfoLog(m_fs_id, 512, NULL, buffer); | |
printf(buffer); | |
} | |
glAttachShader(m_program, m_vs_id); | |
glAttachShader(m_program, m_fs_id); | |
glLinkProgram(m_program); | |
glGenVertexArrays(1, &m_vao); | |
glBindVertexArray(m_vao); | |
glEnable(GL_TEXTURE0); | |
} | |
bool Chip8::LoadROM(const std::string& name) | |
{ | |
std::ifstream fin(name,std::ios::binary); | |
if(fin.fail()) | |
return false; | |
//get filesize | |
fin.seekg(0,std::ios::end); | |
auto romsize = fin.tellg(); | |
fin.seekg(0,std::ios::beg); | |
fin.read((char*)(m_memory+0x200),romsize); | |
return true; | |
} | |
void Chip8::EmulateCycle() | |
{ | |
m_opcode = m_memory[m_pc] << 8 | m_memory[m_pc + 1]; | |
printf("Current opcode: 0x%X\n", m_opcode); | |
switch(m_opcode & 0xF000) | |
{ | |
case 0x0000: | |
switch(m_opcode & 0x000F) | |
{ | |
case 0x0000: // 0x00E0: Clears the screen | |
memset(m_gfx,0,sizeof(m_gfx)); | |
m_drawFlag = true; | |
break; | |
case 0x000E: // 0x00EE: Returns from subroutine | |
--m_sp; | |
m_pc = m_stack[m_sp]; | |
break; | |
default: | |
printf("Unknown opcode [0x0000]: 0x%X\n", m_opcode); | |
} | |
m_pc += 2; | |
break; | |
case 0x1000://0x1NNN: Jump to adress NNN | |
m_pc= m_opcode & 0x0FFF; | |
break; | |
case 0x2000://0x2NNN: Call subroutine at NNN | |
m_stack[m_sp] = m_pc; | |
m_pc = m_opcode & 0x0FFF; | |
++m_sp; | |
break; | |
case 0x3000://0x3VNN: Skip next instruction if reg V equals NN | |
if(m_reg[(0x0F00&m_opcode)>>8]==(m_opcode&0x00FF)) | |
m_pc+=4; | |
else | |
m_pc+=2; | |
break; | |
case 0x4000://0x4VNN: Skip next instruction if reg V doesn't equals NN | |
if(m_reg[(0x0F00&m_opcode)>>8]!=(m_opcode&0x00FF)) | |
m_pc+=4; | |
else | |
m_pc+=2; | |
break; | |
case 0x5000://0x5VY0: Skip next instruction if reg V quals reg Y | |
if(m_reg[(m_opcode&0x0F00)>>8]==(m_reg[(m_opcode&0x00F0)>>4])) | |
m_pc+=4; | |
else | |
m_pc+=2; | |
break; | |
case 0x6000://0x6VNN: Set the register V to NN | |
m_reg[(m_opcode&0x0F00)>>8]= m_opcode&0x00FF; | |
m_pc+=2; | |
break; | |
case 0x7000: //0x7VNN: Add NN to register V | |
m_reg[(m_opcode&0x0F00)>>8] += m_opcode&0x00FF; | |
m_pc+=2; | |
break; | |
case 0x8000: | |
switch(m_opcode&0x000F) | |
{ | |
case 0x0000://0x8XY0: set reg X to Y | |
m_reg[(m_opcode & 0x0F00)>>8] = m_reg[(m_opcode&0x00F0)>>4]; | |
m_pc += 2; | |
break; | |
case 0x0001://0x8XY1: set reg X to X OR Y | |
m_reg[(m_opcode & 0x0F00)>>8] |= m_reg[(m_opcode&0x00F0)>>4]; | |
m_pc += 2; | |
break; | |
case 0x0002://0x8XY2: set reg X to X AND Y | |
m_reg[(m_opcode & 0x0F00)>>8] &= m_reg[(m_opcode&0x00F0)>>4]; | |
m_pc += 2; | |
break; | |
case 0x0003://0x8XY3: set reg X to X XOR Y | |
m_reg[(m_opcode & 0x0F00)>>8] ^= m_reg[(m_opcode&0x00F0)>>4]; | |
m_pc += 2; | |
break; | |
case 0x0004://0x8XY4: Adds reg Y to reg X. reg F is set to 1 when there's a carry, and to 0 when there isn't | |
if(m_reg[(m_opcode & 0x00F0) >> 4] > (0xFF - m_reg[(m_opcode & 0x0F00) >> 8])) | |
m_reg[0xF] = 1; | |
else | |
m_reg[0xF] = 0; | |
m_reg[(m_opcode & 0x0F00) >> 8] += m_reg[(m_opcode & 0x00F0) >> 4]; | |
m_pc += 2; | |
break; | |
case 0x0005://0x8XY5: reg Y is subtracted from reg X. reg F is set to 0 when there's a borrow, and 1 when there isn't | |
if(m_reg[(m_opcode & 0x00F0) >> 4] > m_reg[(m_opcode & 0x0F00) >> 8]) | |
m_reg[0xF] = 0; | |
else | |
m_reg[0xF] = 1; | |
m_reg[(m_opcode & 0x0F00) >> 8] -= m_reg[(m_opcode & 0x00F0) >> 4]; | |
m_pc += 2; | |
break; | |
case 0x0006://0x8XY6: Shifts reg X right by one. reg F is set to the value of the least significant bit of reg X before the shift | |
m_reg[0xF] = m_reg[(m_opcode & 0x0F00) >> 8] & 0x1; | |
m_reg[(m_opcode & 0x0F00) >> 8] >>= 1; | |
m_pc += 2; | |
break; | |
case 0x0007://0x8XY7: Sets reg X to regY minus reg X. reg F is set to 0 when there's a borrow, and 1 when there isn't | |
if(m_reg[(m_opcode & 0x0F00) >> 8] > m_reg[(m_opcode & 0x00F0) >> 4]) | |
m_reg[0xF] = 0; | |
else | |
m_reg[0xF] = 1; | |
m_reg[(m_opcode & 0x0F00) >> 8] = m_reg[(m_opcode & 0x00F0) >> 4] - m_reg[(m_opcode & 0x0F00) >> 8]; | |
m_pc += 2; | |
break; | |
case 0x000E://0x8XYE: Shifts reg X left by one. reg F is set to the value of the most significant bit of regX before the shift | |
m_reg[0xF] = m_reg[(m_opcode & 0x0F00) >> 8] >> 7; | |
m_reg[(m_opcode & 0x0F00) >> 8] <<= 1; | |
m_pc += 2; | |
break; | |
default: | |
printf ("Unknown opcode [0x8000]: 0x%X\n", m_opcode); | |
} | |
case 0x9000://0x9XY0: Skip next instruction if x doesn't equal y | |
if(m_reg[(0x0F00&m_opcode)>>8]!=m_reg[(0x00F0&m_opcode)>>4]) | |
m_pc+=4; | |
else | |
m_pc+=2; | |
break; | |
case 0xA000://0xANNN: Set index to NNN | |
m_index = m_opcode & 0x0FFF; | |
m_pc+=2; | |
break; | |
case 0xB000://0xBNNN: Jump to NNN plus reg 0 | |
m_pc = (m_opcode&0x0FFF)+m_reg[0]; | |
break; | |
case 0xC000://0xCXNN: Set reg X to a random numer masked by NN | |
m_reg[(m_opcode & 0x0F00) >> 8] = (rand() % 0xFF) & (m_opcode & 0x00FF); | |
m_pc+=2; | |
break; | |
case 0xD000: | |
{ | |
uint16_t x = m_reg[(m_opcode & 0x0F00) >> 8]; | |
uint16_t y = m_reg[(m_opcode & 0x00F0) >> 4]; | |
uint16_t height = m_opcode & 0x000F; | |
uint16_t pixel; | |
m_reg[0xF] = 0; | |
for (uint16_t yline = 0; yline < height; yline++) | |
{ | |
pixel = m_memory[m_index + yline]; | |
for(uint16_t xline = 0; xline < 8; xline++) | |
{ | |
if((pixel & (0x80 >> xline)) != 0) | |
{ | |
if(m_gfx[(x + xline + ((y + yline) * 64))] == 1) | |
{ | |
//Collision detected | |
m_reg[0xF] = 1; | |
} | |
m_gfx[x + xline + ((y + yline) * 64)] ^= 1; | |
} | |
} | |
} | |
m_drawFlag = true; | |
m_pc += 2; | |
} | |
break; | |
case 0xE000: | |
switch(m_opcode & 0x00FF) | |
{ | |
case 0x009E://EX9E: Skips the next instruction if the key stored in reg X is pressed | |
if(m_key[m_reg[(m_opcode & 0x0F00) >> 8]] != 0) | |
m_pc += 4; | |
else | |
m_pc += 2; | |
break; | |
case 0x00A1://EXA1: Skips the next instruction if the key stored in reg X isn't pressed | |
if(m_key[m_reg[(m_opcode & 0x0F00) >> 8]] == 0) | |
m_pc += 4; | |
else | |
m_pc += 2; | |
break; | |
default: | |
printf ("Unknown opcode [0xE000]: 0x%X\n", m_opcode); | |
} | |
break; | |
case 0xF000: | |
switch(m_opcode & 0x00FF) | |
{ | |
case 0x0007://FX07: Sets reg X to the value of the delay timer | |
m_reg[(m_opcode & 0x0F00) >> 8] = m_delayTimer; | |
m_pc += 2; | |
break; | |
case 0x000A://FX0A: A key press is awaited, and then stored in reg X | |
{ | |
bool keyPress = false; | |
for(uint8_t i = 0; i < 16; ++i) | |
if(m_key[i] != 0) | |
{ | |
m_reg[(m_opcode & 0x0F00) >> 8] = i; | |
keyPress = true; | |
} | |
//If we didn't received a keypress, skip this cycle and try again. | |
if(!keyPress) | |
return; | |
m_pc += 2; | |
} | |
break; | |
case 0x0015://FX15: Sets the delay timer to reg X | |
m_delayTimer = m_reg[(m_opcode & 0x0F00) >> 8]; | |
m_pc += 2; | |
break; | |
case 0x0018://FX18: Sets the sound timer to reg X | |
m_soundTimer = m_reg[(m_opcode & 0x0F00) >> 8]; | |
m_pc += 2; | |
break; | |
case 0x001E://FX1E: Adds reg X to I | |
if(m_index + m_reg[(m_opcode & 0x0F00) >> 8] > 0xFFF) | |
m_reg[0xF] = 1; | |
else | |
m_reg[0xF] = 0; | |
m_index += m_reg[(m_opcode & 0x0F00) >> 8]; | |
m_pc += 2; | |
break; | |
case 0x0029://FX29: Sets Index to the location of the sprite for the character in reg X. Characters 0-F (in hexadecimal) are represented by a 4x5 font | |
m_index = m_reg[(m_opcode & 0x0F00) >> 8] * 0x5; | |
m_pc += 2; | |
break; | |
case 0x0033://FX33: Stores the Binary-coded decimal representation of regX at the addresses I, I plus 1, and I plus 2 | |
m_memory[m_index] = m_reg[(m_opcode & 0x0F00) >> 8] / 100; | |
m_memory[m_index + 1] = (m_reg[(m_opcode & 0x0F00) >> 8] / 10) % 10; | |
m_memory[m_index + 2] = (m_reg[(m_opcode & 0x0F00) >> 8] % 100) % 10; | |
m_pc += 2; | |
break; | |
case 0x0055://FX55: Stores reg0 to regX in memory starting at address index | |
std::copy(m_reg,m_reg+((m_opcode & 0x0F00) >> 8),&m_memory[m_index]); | |
m_index += ((m_opcode & 0x0F00) >> 8) + 1; | |
m_pc += 2; | |
break; | |
case 0x0065://FX65: Fills reg0 to regX with values from memory starting at address I | |
std::copy(&m_memory[m_index],(&m_memory[m_index])+((m_opcode & 0x0F00) >> 8),m_reg); | |
m_index += ((m_opcode & 0x0F00) >> 8) + 1; | |
m_pc += 2; | |
break; | |
default: | |
printf ("Unknown opcode [0xF000]: 0x%X\n", m_opcode); | |
} | |
break; | |
default: | |
printf ("Unknown opcode: 0x%X\n", m_opcode); | |
} | |
if (Draw()) | |
{ | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 64, 32, 0, GL_RED, GL_UNSIGNED_BYTE, m_gfx); | |
} | |
glDrawArrays(GL_QUADS, 0, 4); | |
if(m_delayTimer>0) | |
--m_delayTimer; | |
if (m_soundTimer > 0) | |
{ | |
if (m_soundTimer == 1) | |
m_beep.play(); | |
--m_soundTimer; | |
} | |
//sleep | |
auto current = std::chrono::high_resolution_clock::now(); | |
auto fill_time = m_cycleInterval - (current - m_last); | |
if (fill_time > std::chrono::microseconds(0)) | |
std::this_thread::sleep_for(fill_time); | |
else | |
int a = 0; | |
m_last = current; | |
} | |
bool Chip8::Draw() | |
{ | |
if(m_drawFlag) | |
{ | |
m_drawFlag=false; | |
return true; | |
} | |
return false; | |
} | |
void Chip8::HandleKey(sf::Event::KeyEvent & key) | |
{ | |
switch (key.code) | |
{ | |
case sf::Keyboard::Num1: | |
case sf::Keyboard::Num2: | |
case sf::Keyboard::Num3: | |
case sf::Keyboard::Num4: | |
m_key[key.code - 27] = 1; | |
break; | |
case sf::Keyboard::Q: | |
m_key[4] = 1; | |
break; | |
case sf::Keyboard::W: | |
m_key[5] = 1; | |
break; | |
case sf::Keyboard::E: | |
m_key[6] = 1; | |
break; | |
case sf::Keyboard::R: | |
m_key[7] = 1; | |
break; | |
case sf::Keyboard::A: | |
m_key[8] = 1; | |
break; | |
case sf::Keyboard::S: | |
m_key[9] = 1; | |
break; | |
case sf::Keyboard::D: | |
m_key[10] = 1; | |
break; | |
case sf::Keyboard::F: | |
m_key[11] = 1; | |
break; | |
case sf::Keyboard::Y: | |
m_key[12] = 1; | |
break; | |
case sf::Keyboard::X: | |
m_key[13] = 1; | |
break; | |
case sf::Keyboard::C: | |
m_key[14] = 1; | |
break; | |
case sf::Keyboard::V: | |
m_key[15] = 1; | |
break; | |
default: | |
break; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment