Archived
1
Fork 0
This repository has been archived on 2025-04-12. You can view files and clone it, but cannot push or open issues or pull requests.
chip8/src/main.cpp
2020-04-29 14:47:33 -04:00

353 lines
11 KiB
C++

#include <iostream>
#include <iomanip>
#include <cstdio>
#include <climits>
#include <cmath>
#include <SDL.h>
#include <map>
#include <filesystem>
#include <vector>
#include <array>
#include "emu.hpp"
#include "glad/glad.h"
#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_opengl3.h"
#include "compiler.hpp"
constexpr std::array<uint8_t, 80> chip8_fontset = {
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
};
const std::map<SDL_Scancode, int> scancodes = {
{SDL_SCANCODE_0, 0},
{SDL_SCANCODE_1, 1},
{SDL_SCANCODE_2, 2},
{SDL_SCANCODE_3, 3},
{SDL_SCANCODE_4, 4},
{SDL_SCANCODE_5, 5},
{SDL_SCANCODE_6, 6},
{SDL_SCANCODE_KP_0, 0},
{SDL_SCANCODE_KP_1, 1},
{SDL_SCANCODE_KP_2, 2},
{SDL_SCANCODE_KP_3, 3},
{SDL_SCANCODE_KP_4, 4},
{SDL_SCANCODE_KP_5, 5},
{SDL_SCANCODE_KP_6, 6}
};
bool is_rom_open = false;
void load_rom(const char* path) {
state.reset();
memcpy(state.memory, chip8_fontset.data(), chip8_fontset.size());
FILE* file = fopen(path, "rb");
fseek(file, 0L, SEEK_END);
auto sz = ftell(file);
fseek(file, 0L, SEEK_SET);
fread(state.memory + program_begin, sz, 1, file);
is_rom_open = true;
}
std::string get_short_debug_string(uint16_t opcode) {
if(opcode == 0)
return "invalid opcode";
const uint16_t id = opcode >> 12;
const uint8_t nn = opcode & 0x00ff;
switch(id) {
case 0x0:
{
if(nn == 0xE0) {
return "clear screen";
}
}
break;
case 0x2:
{
return "call subroutine";
}
break;
case 0x6:
{
return "v[x] = nn";
}
break;
case 0xD:
return "display sprite";
}
return "";
}
GLuint quad_vao = 0;
GLuint pixel_program = 0;
GLuint pixels_texture = 0;
void setup_gfx() {
// create quad for pixel rendering
constexpr std::array vertices = {
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f
};
constexpr std::array elements = {
0, 1, 2,
2, 3, 0
};
glGenVertexArrays(1, &quad_vao);
glBindVertexArray(quad_vao);
GLuint vbo = 0;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), reinterpret_cast<void*>(3 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
GLuint ebo = 0;
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elements.size() * sizeof(uint32_t), elements.data(), GL_STATIC_DRAW);
constexpr std::string_view vertex_glsl =
"#version 330 core\n"
"layout (location = 0) in vec3 in_position;\n"
"layout (location = 1) in vec2 in_uv;\n"
"out vec2 uv;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(in_position.xyz, 1.0);\n"
" uv = in_uv;\n"
"}\n";
const char* vertex_src = vertex_glsl.data();
constexpr std::string_view fragment_glsl =
"#version 330 core\n"
"in vec2 uv;\n"
"out vec4 out_color;\n"
"uniform sampler2D pixel_texture;\n"
"void main()\n"
"{\n"
" out_color.rgb = vec3(1) * (texture(pixel_texture, uv).r * 255);\n"
"}\n";
const char* fragment_src = fragment_glsl.data();
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_src, nullptr);
glCompileShader(vertex_shader);
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_src, nullptr);
glCompileShader(fragment_shader);
pixel_program = glCreateProgram();
glAttachShader(pixel_program, vertex_shader);
glAttachShader(pixel_program, fragment_shader);
glLinkProgram(pixel_program);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glGenTextures(1, &pixels_texture);
glBindTexture(GL_TEXTURE_2D, pixels_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, screen_width, screen_height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
}
int main(int argc, char* argv[]) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_Window* window = SDL_CreateWindow("chip8", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1);
gladLoadGL();
setup_gfx();
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
ImGui_ImplOpenGL3_Init("#version 330 core");
std::vector<std::string> rom_paths;
for(auto& p: std::filesystem::directory_iterator("roms/"))
rom_paths.push_back(p.path());
bool running = true;
while(running) {
SDL_Event event = {};
while(SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event);
if(event.type == SDL_QUIT)
running = false;
if(event.type == SDL_KEYDOWN) {
if(scancodes.count(event.key.keysym.scancode))
state.keys[scancodes.at(event.key.keysym.scancode)] = 1;
}
if(event.type == SDL_KEYUP) {
if(scancodes.count(event.key.keysym.scancode))
state.keys[scancodes.at(event.key.keysym.scancode)] = 0;
}
}
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(window);
ImGui::NewFrame();
if(ImGui::BeginMainMenuBar()) {
if(ImGui::BeginMenu("File")) {
if(ImGui::BeginMenu("Open ROM...")) {
for(auto& rom : rom_paths) {
if(ImGui::Button(rom.c_str()))
load_rom(rom.c_str());
}
ImGui::EndMenu();
}
if(ImGui::MenuItem("Compile & Run Program")) {
state.reset();
memcpy(state.memory, chip8_fontset.data(), chip8_fontset.size());
compile();
load_compiled_rom();
is_rom_open = true;
}
ImGui::EndMenu();
}
if(ImGui::BeginMenu("Options")) {
ImGui::MenuItem("Enable Anti-flicker", nullptr, &options.enable_anti_flicker);
ImGui::MenuItem("Emulate Original CHIP-8", nullptr, &options.emulate_original);
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if(state.delay_timer > 0)
state.delay_timer--;
const uint16_t opcode = (state.memory[state.PC] << 8) | state.memory[state.PC + 1];
if(is_rom_open && !pause_execution)
process_opcode(opcode);
if(ImGui::Begin("Memory")) {
for(int i = 0; i < 16; i++)
ImGui::Text("V[%i] = %i", i, state.v[i]);
for(int i = program_begin; i < 4096; i++)
ImGui::Text("mem[%i] = %i", i, state.memory[i]);
}
ImGui::End();
if(ImGui::Begin("Debugger")) {
if(ImGui::Button(pause_execution ? "Play" : "Pause"))
pause_execution = !pause_execution;
ImGui::SameLine();
if(ImGui::Button("Step"))
process_opcode(opcode);
static bool enable_auto_scroll = true;
ImGui::Checkbox("Enable auto scroll", &enable_auto_scroll);
ImGui::BeginChild("progam_edit", ImVec2(-1, -1), true);
for(int i = program_begin; i < 4096; i += 2) {
std::string s;
s.reserve(50);
uint16_t opcode = (state.memory[i] << 8) | state.memory[i + 1];
auto debug_string = get_short_debug_string(opcode);
sprintf(s.data(), "[0x%02X] 0x%04X ; %s", i, opcode, debug_string.c_str());
ImGui::Selectable(s.c_str(), state.PC == i);
if(state.PC == i && enable_auto_scroll && !pause_execution && is_rom_open) {
ImGui::SetScrollHereY();
}
}
ImGui::EndChild();
}
ImGui::End();
if(state.draw_dirty) {
glBindTexture(GL_TEXTURE_2D, pixels_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, screen_width, screen_height, 0, GL_RED, GL_UNSIGNED_BYTE, state.pixels);
glBindTexture(GL_TEXTURE_2D, 0);
state.draw_dirty = false;
}
ImGui::Render();
auto& io = ImGui::GetIO();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(pixel_program);
glBindVertexArray(quad_vao);
glBindTexture(GL_TEXTURE_2D, pixels_texture);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);
}
return 0;
}