emulator/src/main.cpp
2021-01-14 20:31:32 +01:00

225 lines
4.6 KiB
C++

#include <bits/stdint-uintn.h>
#include <cstring>
#include <fstream>
#include <stdexcept>
#include <array>
#include <filesystem>
#include <functional>
#include <SDL2/SDL.h>
#include <SDL2/SDL_error.h>
#include <SDL2/SDL_hints.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_log.h>
#include <SDL2/SDL_main.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_video.h>
#include <string>
#include "Z/types/base.h"
#include "Z80.h"
template <int W, int H>
class Emulator {
public:
Emulator(std::filesystem::path rom_name) {
// SDL
int render_flags = SDL_RENDERER_ACCELERATED;
int window_flags = 0;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
throw std::runtime_error("Failed to initialize SDL2: " + std::string(SDL_GetError()));
}
window = SDL_CreateWindow("Z80 Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, W*16, H*16, window_flags);
if (!window) {
throw std::runtime_error("Failed to open window: " + std::string(SDL_GetError()));
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
renderer = SDL_CreateRenderer(window, -1, render_flags);
if (!window) {
throw std::runtime_error("Failed to create rendere: " + std::string(SDL_GetError()));
}
if (!IMG_Init(IMG_INIT_PNG)) {
throw std::runtime_error("Failed to initialize SDL2_image: " + std::string(SDL_GetError()));
}
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Loading '../convert-font/font.png'");
font = IMG_LoadTexture(renderer, "../convert-font/font.png");
if (!font) {
throw std::runtime_error("Failed to open font: " + std::string(IMG_GetError()));
}
// Z80
std::ifstream rom_file(rom_name, std::ios::in | std::ios::binary);
rom_file.seekg(0, std::ios::end);
size_t rom_size = rom_file.tellg();
rom_file.seekg(0, std::ios::beg);
rom_file.read(reinterpret_cast<char*>(memory.data()), rom_size);
z.context = this;
z.read = z_read;
z.write = z_write;
z.in = z_in;
z.out = z_out;
z.int_data = z_int_data;
z80_reset(&z);
}
~Emulator() {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
static zuint8 z_read(void* context, zuint16 address) {
auto e = (Emulator<W,H>*)context;
return e->memory[address];
}
static void z_write(void* context, zuint16 address, zuint8 value) {
auto e = (Emulator<W,H>*)context;
e->memory[address] = value;
}
static zuint8 z_in(void* /* context */, zuint16 port) {
switch (port & 0xFF) {
case 0x03:
return 0x01;
// char
case 0x1E:
return 0x00;
// has_input
case 0x1F:
return 0x00;
default:
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "IO Read @ 0x%X\n", (port & 0xFF));
return 0x00;
}
}
static void z_out(void* context, zuint16 port, zuint8 value) {
auto e = (Emulator<W,H>*)context;
switch (port & 0xFF) {
case 0x02:
e->input(value);
break;
default:
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "IO Wrte 0x%X @ 0x%X\n", value, (port & 0xFF));
break;
}
}
static zuint32 z_int_data(void* /* context */) {
return 0x00;
}
void update() {
z80_run(&z, 1);
}
// @todo This should handle the input properly (same as fpga)
void input(char c) {
buffer[cursor.y*W + cursor.x] = c;
cursor.x++;
if (cursor.x > W) {
cursor.x = 0;
cursor.y++;
}
}
void draw_buffer() {
SDL_Rect src;
src.w = 16;
src.h = 16;
SDL_Rect dest;
dest.w = 16;
dest.h = 16;
SDL_SetTextureColorMod(font, 255, 255, 255);
for (int x = 0; x < W; ++x) {
for (int y = 0; y < H; ++y) {
char c = buffer[y*W + x];
src.x = (c % 16) * 16;
src.y = (c / 16) * 16;
dest.x = x * 16;
dest.y = y * 16;
SDL_RenderCopy(renderer, font, &src, &dest);
}
}
}
bool handle_events() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
return false;
default:
break;
}
}
return true;
}
void prepare() {
SDL_SetRenderDrawColor(renderer, 96, 128, 255, 255);
SDL_RenderClear(renderer);
}
void render() {
SDL_RenderPresent(renderer);
}
private:
SDL_Renderer* renderer = nullptr;
SDL_Window* window = nullptr;
SDL_Texture* font = nullptr;
std::array<char, W*H> buffer = {0};
struct {
int x = 0;
int y = 0;
} cursor;
Z80 z;
std::array<uint8_t, 0xFFFF> memory;
};
int main() {
Emulator<80, 45> e("../../software/monitor/.build/rom_monitor.bin");
while (e.handle_events()) {
e.prepare();
e.update();
e.draw_buffer();
e.render();
}
}