initial commit

This commit is contained in:
simuuz
2021-11-26 21:07:01 +01:00
commit 7954244af9
154 changed files with 14250 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
#pragma once
#include <core.h>
#include <imgui.h>
#include <imgui_impl_sdl.h>
#include <imgui_impl_opengl3.h>
#include <nfd.hpp>
#include <atomic>
#include <pthread.h>
#include <ctime>
#include <gui/logger.hpp>
#include <gui/opengl_context.hpp>
static const ImVec4 colors_disasm[3] = {ImVec4(1.0, 0.000, 0.0, 1.0), // RED
ImVec4(1.0, 0.619, 0.0, 1.0), // ORANGE
ImVec4(1.0, 0.988, 0.0, 1.0)}; // YELLOW
static const ImVec4 clear_color = {0.45f, 0.55f, 0.60f, 1.00f};
struct Gui {
ImGuiContext* ctx;
bool show_disasm = true, show_regs = true;
bool show_metrics = false, show_logs = true;
bool pause_on_fatal = true;
float log_pos_y = 0;
std::string old_message = "NULL";
message_type old_message_type = INFO;
nfdchar_t* rom_file;
bool rom_loaded = false, running = true;
pthread_t emu_thread;
Logger logger;
clock_t delta;
double fps = 60.0, frametime = 16.0;
std::atomic_bool emu_quit = false;
core_t core;
OpenGLContext context;
Gui(const char* title);
~Gui();
void MainLoop();
void DestroyGui();
void OpenFile();
void MainMenubar();
void DebuggerWindow();
void RegistersView();
void Reset();
void Stop();
void Start();
};

View File

@@ -0,0 +1,15 @@
#pragma once
#include <log.h>
#include <imgui.h>
#include <imgui_logger.h>
#include <string>
static const std::string message_type_strings[3] = {"[INFO]", "[WARNING]", "[FATAL]"};
struct Gui;
struct Logger {
Logger();
void LogWindow(Gui* gui);
ImGui::Logger logger;
};

View File

@@ -0,0 +1,29 @@
#pragma once
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL_opengl.h>
#include <core.h>
#include <imgui.h>
struct Gui;
struct GLData {
u32 old_w, old_h;
u8 old_format = 14;
int glFormat = GL_UNSIGNED_SHORT_5_5_5_1;
u8 depth = 2;
};
struct OpenGLContext {
GLData gl_data;
SDL_Window* window;
unsigned int id; // OpenGL framebuffer texture ID
u8* framebuffer;
SDL_GLContext gl_context;
OpenGLContext(const char* title, const char* m_glsl_version);
void UpdateTexture(core_t* core);
void MainWindow(Gui* gui, core_t* core);
void Update(ImGuiIO& io);
void Frame();
~OpenGLContext();
};

216
src/frontend/src/gui.cpp Normal file
View File

@@ -0,0 +1,216 @@
#include <core.h>
#include <gui.hpp>
#include <utils.h>
#include <string>
#include <cstring>
#define GLSL_VERSION "#version 130"
INLINE void* core_callback(void* args) {
Gui* gui = (Gui*)args;
while(!atomic_load(&gui->emu_quit)) {
clock_t begin = clock();
run_frame(&gui->core);
clock_t end = clock();
gui->delta += end - begin;
}
return NULL;
}
Gui::~Gui() {
free(rom_file);
emu_quit = true;
pthread_join(emu_thread, NULL);
destroy_core(&core);
NFD_Quit();
}
Gui::Gui(const char* title) : context(title, GLSL_VERSION) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
ImGuiStyle& style = ImGui::GetStyle();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
ImGui::StyleColorsDark();
style.WindowRounding = 10;
const char* glsl_version = GLSL_VERSION;
ImGui_ImplSDL2_InitForOpenGL(context.window, context.gl_context);
ImGui_ImplOpenGL3_Init(glsl_version);
io.Fonts->AddFontFromFileTTF("resources/FiraCode-VariableFont_wght.ttf", 16);
init_core(&core);
pthread_create(&emu_thread, NULL, core_callback, (void*)this);
NFD_Init();
}
void Gui::MainLoop() {
ImGuiIO& io = ImGui::GetIO(); (void)io;
unsigned int frames = 0;
while(running) {
SDL_Event event;
while(SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event);
switch(event.type) {
case SDL_QUIT: running = false; break;
case SDL_WINDOWEVENT:
if(event.window.event == SDL_WINDOWEVENT_CLOSE) {
running = false;
}
break;
case SDL_KEYDOWN:
switch(event.key.keysym.sym) {
case SDLK_o: OpenFile(); break;
case SDLK_p:
if(rom_loaded) {
core.running = !core.running;
}
break;
case SDLK_ESCAPE: running = false; break;
}
break;
}
}
context.Frame();
if(show_metrics) ImGui::ShowMetricsWindow(&show_metrics);
DebuggerWindow();
context.MainWindow(this, &core);
frames++;
if(clock_to_ms(delta) > 1000.0) {
fps = (double)frames * 0.5 + fps *0.5;
frames = 0;
delta -= CLOCKS_PER_SEC;
frametime = 1000.0 / ((fps == 0) ? 0.001 : fps);
}
context.Update(io);
}
}
void Gui::MainMenubar() {
ImVec2 window_size = ImGui::GetWindowSize();
if(ImGui::BeginMenuBar()) {
if(ImGui::BeginMenu("File")) {
if(ImGui::MenuItem("Open", "O")) {
OpenFile();
}
if(ImGui::MenuItem("Exit", "Esc")) {
running = false;
}
ImGui::EndMenu();
}
if(ImGui::BeginMenu("Emulation")) {
std::string pause_text = "Pause";
if(!core.running && rom_loaded) {
pause_text = "Resume";
}
if(ImGui::MenuItem(pause_text.c_str(), "P", false, rom_loaded)) {
core.running = !core.running;
if(core.running) {
core.stepping = false;
}
}
if(ImGui::MenuItem("Stop", NULL, false, rom_loaded)) {
Stop();
}
if(ImGui::MenuItem("Reset", NULL, false, rom_loaded)) {
Reset();
}
ImGui::EndMenu();
}
if(ImGui::BeginMenu("Settings")) {
ImGui::MenuItem("Show disassembly", NULL, &show_disasm, true);
ImGui::MenuItem("Show register watch", NULL, &show_regs, true);
ImGui::MenuItem("Show logs", NULL, &show_logs, true);
ImGui::MenuItem("Show metrics", NULL, &show_metrics, true);
ImGui::EndMenu();
}
ImVec2 close_button_size = ImGui::CalcTextSize("[X]");
char fps_text[255];
sprintf(fps_text, rom_loaded ? "[ %.2f fps ][ %.2f ms ]" : "[ NaN fps ][ NaN ms ]", fps, frametime);
ImVec2 fps_size = ImGui::CalcTextSize(fps_text);
ImGuiStyle& style = ImGui::GetStyle();
ImGui::SameLine(window_size.x - close_button_size.x - fps_size.x - style.ItemInnerSpacing.x * 4 - 12, -1);
ImGui::Text("%s", fps_text);
ImGui::SameLine(window_size.x - close_button_size.x - style.ItemInnerSpacing.x * 2 - 12, -1);
if(ImGui::BeginMenu("[X]")) {
running = false;
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
}
void Gui::RegistersView() {
registers_t* regs = &core.cpu.regs;
ImGui::Begin("Registers view", &show_regs, 0);
for(int i = 0; i < 32; i+=4) {
ImGui::Text("%s: %016lX %s: %016lX %s: %016lX %s: %016lX", regs_str[i], regs->gpr[i], regs_str[i + 1], regs->gpr[i + 1], regs_str[i + 2], regs->gpr[i + 2], regs_str[i + 3], regs->gpr[i + 3]);
}
ImGui::Separator();
s64 pipe[3] = {regs->old_pc, regs->pc, regs->next_pc};
for(int i = 0; i < 3; i++) {
ImGui::Text("pipe[%d]: %016lX", i, pipe[i]);
}
ImGui::End();
}
void Gui::DebuggerWindow() {
if(show_regs) RegistersView();
if(show_logs) logger.LogWindow(this);
}
void Gui::OpenFile() {
nfdfilteritem_t filter = { "Nintendo 64 roms", "n64,z64,v64,N64,Z64,V64" };
nfdresult_t result = NFD_OpenDialog(&rom_file, &filter, 1, EMU_DIR);
if(result == NFD_OKAY) {
Reset();
}
}
void Gui::Start() {
rom_loaded = load_rom(&core.mem, rom_file);
emu_quit = !rom_loaded;
core.running = rom_loaded;
if(rom_loaded) {
pthread_create(&emu_thread, NULL, core_callback, (void*)this);
}
}
void Gui::Reset() {
Stop();
Start();
}
void Gui::Stop() {
emu_quit = true;
pthread_join(emu_thread, NULL);
init_core(&core);
rom_loaded = false;
core.running = false;
}

View File

@@ -0,0 +1,27 @@
#include <gui/logger.hpp>
#include <gui.hpp>
Logger::Logger() {
logger.InfoStr = message_type_strings[0].c_str();
logger.WarnStr = message_type_strings[1].c_str();
logger.ErrorStr = message_type_strings[2].c_str();
}
void Logger::LogWindow(Gui* gui) {
std::string final_message{};
if(last_message != nullptr && strcmp(last_message, "") && strcmp(last_message, gui->old_message.c_str())) {
if(last_message_type == FATAL) {
if(gui->pause_on_fatal) {
gui->core.running = false;
} else {
gui->Stop();
}
}
gui->old_message = std::string(last_message);
gui->old_message_type = last_message_type;
final_message = message_type_strings[last_message_type] + " " + gui->old_message;
logger.AddLog("%s", final_message.c_str());
}
logger.Draw("Logs", &gui->show_logs);
}

View File

@@ -0,0 +1,146 @@
#include <gui/opengl_context.hpp>
#include <log.h>
#include <gui.hpp>
OpenGLContext::~OpenGLContext() {
free(framebuffer);
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_GL_DeleteContext(gl_context);
SDL_DestroyWindow(window);
SDL_Quit();
}
OpenGLContext::OpenGLContext(const char* title, const char* m_glsl_version) {
if(SDL_Init(SDL_INIT_VIDEO) != 0) {
logfatal("Error: %s\n", SDL_GetError());
}
const char* glsl_version = m_glsl_version;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_DisplayMode mode;
SDL_GetCurrentDisplayMode(0, &mode);
int w = mode.w - (mode.w / 4), h = mode.h - (mode.h / 4);
window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
gl_context = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(0); // Enable vsync
framebuffer = (u8*)malloc(320 * 240 * 4);
memset(framebuffer, 0x000000ff, 320 * 240 * 4);
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 320, 240, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, framebuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
}
ImVec2 image_size;
INLINE void resize_callback(ImGuiSizeCallbackData* data) {
ImVec2 window_size = ImGui::GetWindowSize();
float x = window_size.x - 15, y = window_size.y - 15;
float current_aspect_ratio = x / y;
if(N64_ASPECT_RATIO > current_aspect_ratio) {
y = x / (N64_ASPECT_RATIO);
} else {
x = y * (N64_ASPECT_RATIO);
}
image_size.x = x;
image_size.y = y - 30;
}
void OpenGLContext::UpdateTexture(core_t* core) {
u32 w = core->mem.mmio.vi.width, h = 0.75 * w;
u32 origin = core->mem.mmio.vi.origin & 0xFFFFFF;
u8 format = core->mem.mmio.vi.status.format;
bool reconstruct_texture = false;
bool res_changed = gl_data.old_w != w || gl_data.old_h != h;
bool format_changed = gl_data.old_format != format;
if(res_changed) {
gl_data.old_w = w;
gl_data.old_h = h;
reconstruct_texture = true;
}
if(format_changed) {
gl_data.old_format = format;
if(format == f5553) {
gl_data.glFormat = GL_UNSIGNED_SHORT_5_5_5_1;
gl_data.depth = 2;
} else if (format == f8888) {
gl_data.glFormat = GL_UNSIGNED_INT_8_8_8_8;
gl_data.depth = 4;
}
reconstruct_texture = true;
}
if(reconstruct_texture) {
framebuffer = (u8*)realloc(framebuffer, w * h * gl_data.depth);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, gl_data.glFormat, framebuffer);
}
if(format == f8888) {
framebuffer[4] = 0xff;
memcpy(framebuffer, &core->mem.rdram[origin & RDRAM_DSIZE], w * h * gl_data.depth);
for(int i = 0; i < w * h * gl_data.depth; i += gl_data.depth) {
framebuffer[i + 4] |= 0xff;
}
} else {
framebuffer[1] |= 1;
for(int i = 0; i < w * h * gl_data.depth; i += gl_data.depth) {
framebuffer[i] = core->mem.rdram[HALF_ADDR(origin + i & RDRAM_DSIZE)];
framebuffer[i + 1] = core->mem.rdram[HALF_ADDR(origin + 1 + i & RDRAM_DSIZE)] | (1 << 16);
}
}
glBindTexture(GL_TEXTURE_2D, id);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, gl_data.glFormat, framebuffer);
}
void OpenGLContext::MainWindow(Gui* gui, core_t *core) {
ImGui::SetNextWindowSizeConstraints((ImVec2){0, 0}, (ImVec2){__FLT_MAX__, __FLT_MAX__}, resize_callback, NULL);
ImGui::Begin("Display", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking);
gui->MainMenubar();
UpdateTexture(core);
ImVec2 window_size = ImGui::GetWindowSize();
ImVec2 result = {static_cast<float>((window_size.x - image_size.x) * 0.5), static_cast<float>((window_size.y - image_size.y + 15) * 0.5)};
ImGui::SetCursorPos(result);
ImGui::Image((ImTextureID)((intptr_t)id), image_size, (ImVec2){0, 0}, (ImVec2){1, 1}, (ImVec4){1, 1, 1, 1}, (ImVec4){0, 0, 0, 0});
ImGui::End();
}
void OpenGLContext::Frame() {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
}
void OpenGLContext::Update(ImGuiIO& io) {
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);
}

7
src/main.cpp Normal file
View File

@@ -0,0 +1,7 @@
#include <gui.hpp>
int main(int argc, char* argv[]) {
Gui gui("懐かしい");
gui.MainLoop();
return 0;
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
#define PACKED __attribute__((__packed__))
#define INLINE static inline __attribute__((always_inline))
#define GB_ASPECT_RATIO (float)10 / 9
#define BOOTROM_SIZE 0x100
#define BOOTROM_DSIZE (BOOTROM_SIZE - 1)
#define EXTRAM_SIZE 0x2000
#define EXTRAM_DSIZE (EXTRAM_SIZE - 1)
#define WRAM_SIZE 0x2000
#define WRAM_DSIZE (WRAM_SIZE - 1)
#define HRAM_SIZE 0x7f
#define HRAM_DSIZE (HRAM_SIZE - 1)
#define ROM_SIZE_MIN 0x8000
#define ROM_DSIZE_MIN (ROM_SIZE_MIN - 1)
#ifndef EMU_DIR
#define EMU_DIR ""
#endif

View File

@@ -0,0 +1,21 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <core/cpu.h>
#include <core/mem.h>
typedef struct core_t {
cpu_t cpu;
mem_t mem;
u32 break_addr;
bool running, stepping;
} core_t;
void init_core(core_t* core);
void destroy_core(core_t* core);
void run_frame(core_t* core);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,21 @@
#pragma once
#include <mem.h>
#include <registers.h>
typedef struct {
bool halt, skip, ime, ei;
u8 cycles, opcode;
registers_t registers;
} cpu_t;
void init_cpu(cpu_t* cpu, bool skip);
void destroy_cpu(cpu_t *cpu);
void handle_interrupts(cpu_t* cpu);
INLINE void reset(cpu_t* cpu) {
bool skip = cpu->skip;
destroy_cpu(cpu);
init_cpu(cpu, skip);
}
u8 step_cpu(cpu_t* cpu);

View File

@@ -0,0 +1,113 @@
#pragma once
#include <utils.h>
#include <mem.h>
typedef struct {
REGS_UNION(a, f);
REGS_UNION(b, c);
REGS_UNION(d, e);
REGS_UNION(h, l);
u16 sp, pc;
} registers_t;
INLINE bool get_cond(registers_t* regs, u8 opcode) {
if (opcode & 1) {
return true;
}
u8 bits = (opcode >> 3) & 3;
switch (bits) {
case 0: return !regs->f.z;
case 1: return regs->f.z;
case 2: return !regs->f.c;
case 3: return regs->f.c;
}
}
INLINE void reg_push(registers_t* regs, mem_t* mem, u16 val) {
regs->sp -= 2;
write16(mem, regs->sp, val);
}
INLINE u16 reg_pop(registers_t* regs, mem_t* mem) {
u16 val = read16(mem, regs->sp);
regs->sp += 2;
return val;
}
INLINE u8 read_reg8(registers_t* regs, mem_t* mem, u8 bits) {
switch (bits) {
case 0: return regs->b;
case 1: return regs->c;
case 2: return regs->d;
case 3: return regs->e;
case 4: return regs->h;
case 5: return regs->l;
case 6: return read8(mem, regs->hl);
case 7: return regs->a;
}
}
INLINE void write_reg8(registers_t* regs, mem_t* mem, u8 bits, u8 val) {
switch (bits) {
case 0: regs->b = val; break;
case 1: regs->c = val; break;
case 2: regs->d = val; break;
case 3: regs->e = val; break;
case 4: regs->h = val; break;
case 5: regs->l = val; break;
case 6: write8(mem, regs->hl, val); break;
case 7: regs->a = val; break;
}
}
INLINE u16 read_reg16(registers_t* regs, int group, u8 bits) {
if (group == 1) {
switch (bits) {
case 0: return regs.bc;
case 1: return regs.de;
case 2: return regs.hl;
case 3: return regs.sp;
}
} else if (group == 2) {
switch (bits) {
case 0: return regs.bc;
case 1: return regs.de;
case 2: case 3: return regs.hl;
}
} else if (group == 3) {
switch (bits) {
case 0: return regs.bc;
case 1: return regs.de;
case 2: return regs.hl;
case 3: return regs.af;
}
}
}
INLINE void write_reg16(registers_t* regs, int group, u8 bits, u16 value) {
if (group == 1) {
switch (bits) {
case 0: regs.bc = value; break;
case 1: regs.de = value; break;
case 2: regs.hl = value; break;
case 3: regs.sp = value; break;
}
} else if (group == 2) {
switch (bits) {
case 0: regs.bc = value; break;
case 1: regs.de = value; break;
case 2: case 3: regs.hl = value; break;
}
} else if (group == 3) {
switch (bits) {
case 0: regs.bc = value; break;
case 1: regs.de = value; break;
case 2: regs.hl = value; break;
case 3:
regs->a = value >> 8;
regs->f = value;
break;
}
}
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <common.h>
typedef struct {
union {
struct {
unsigned:2;
unsigned select_btn:1;
unsigned select_dpad:1;
unsigned btn_start_down:1;
unsigned btn_select_up:1;
unsigned btn_a_left:1;
unsigned btn_b_right:1;
} PACKED;
u8 raw;
};
bool dpad, button;
} joyp_t;
INLINE void joyp_write(joyp_t* joyp, u8 val) {
joyp->raw = val;
joyp->raw |= 0b11000000;
}
typedef struct {
u8 bootrom, intf;
u8 nr41, nr42, nr43, nr44, nr50, nr51, nr52;
joyp_t joyp;
} io_t;
typedef struct {
u8 ie;
io_t io;
u8 bootrom[BOOTROM_SIZE];
u8 wram[WRAM_SIZE];
u8 hram[HRAM_SIZE];
} mem_t;

View File

@@ -0,0 +1,9 @@
#pragma once
#define REGS_UNION(f, s) \
union { \
struct { \
u8 ##s; \
u8 ##f; \
} PACKED; \
u16 ##fs; \
};