2018-06-22 20:51:16 +01:00
|
|
|
import std.stdio;
|
|
|
|
import derelict.sdl2.sdl;
|
2018-06-22 23:54:39 +01:00
|
|
|
import derelict.imgui.imgui;
|
2018-06-22 20:51:16 +01:00
|
|
|
import glad.gl.all;
|
|
|
|
|
2018-06-22 23:54:39 +01:00
|
|
|
import window;
|
|
|
|
import imgui;
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
bool checkShaderError(GLuint shader, GLuint flag, bool is_program, in char[] shader_path) nothrow {
|
|
|
|
|
|
|
|
GLint result;
|
|
|
|
|
|
|
|
(is_program) ? glGetProgramiv(shader, flag, &result)
|
|
|
|
: glGetShaderiv(shader, flag, &result);
|
|
|
|
|
|
|
|
if (result == GL_FALSE) {
|
|
|
|
|
|
|
|
GLchar[256] log; //FIXME this is potentially fatal
|
|
|
|
(is_program) ? glGetProgramInfoLog(shader, log.sizeof, null, log.ptr)
|
|
|
|
: glGetShaderInfoLog(shader, log.sizeof, null, log.ptr);
|
|
|
|
|
|
|
|
printf("[OpenGL] Error in %s: %s\n", shader_path.ptr, log.ptr);
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} // checkShaderError
|
|
|
|
|
|
|
|
struct PixelBuffer {
|
|
|
|
|
|
|
|
struct Shader {
|
|
|
|
|
|
|
|
private {
|
|
|
|
GLuint shader_prog;
|
|
|
|
}
|
|
|
|
|
|
|
|
@disable this(this);
|
|
|
|
|
|
|
|
void compile(const (GLchar*)* vs_source, const (GLchar*)* fs_source) {
|
|
|
|
|
|
|
|
import core.stdc.stdlib : exit;
|
|
|
|
|
|
|
|
GLuint new_vs_shader = glCreateShader(GL_VERTEX_SHADER);
|
|
|
|
glShaderSource(new_vs_shader, 1, vs_source, null);
|
|
|
|
glCompileShader(new_vs_shader);
|
|
|
|
if (!checkShaderError(new_vs_shader, GL_COMPILE_STATUS, false, "vertex_shader")) {
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
GLuint new_fs_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
|
|
glShaderSource(new_fs_shader, 1, fs_source, null);
|
|
|
|
glCompileShader(new_fs_shader);
|
|
|
|
if (!checkShaderError(new_fs_shader, GL_COMPILE_STATUS, false, "fragment_shader")) {
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
GLuint new_shader = glCreateProgram();
|
|
|
|
glAttachShader(new_shader, new_vs_shader);
|
|
|
|
glAttachShader(new_shader, new_fs_shader);
|
|
|
|
|
|
|
|
// glBindAttribLocation(new_shader, 0, "screen_size");
|
|
|
|
|
|
|
|
glLinkProgram(new_shader);
|
|
|
|
if (!checkShaderError(new_shader, GL_LINK_STATUS, true, "shader_program")) {
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
glValidateProgram(new_shader);
|
|
|
|
if (!checkShaderError(new_shader, GL_VALIDATE_STATUS, true, "shader_program")) {
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
shader_prog = new_shader;
|
|
|
|
|
|
|
|
} // compile
|
|
|
|
|
|
|
|
void setUniforms(int w, int h) {
|
|
|
|
auto attr_loc = glGetUniformLocation(shader_prog, "screen_size");
|
|
|
|
glUniform2f(attr_loc, cast(float)w, cast(float)h);
|
|
|
|
} // setUniforms
|
|
|
|
|
|
|
|
void bind() {
|
|
|
|
glUseProgram(shader_prog);
|
|
|
|
} // bind
|
|
|
|
|
|
|
|
void unbind() {
|
|
|
|
glUseProgram(0);
|
|
|
|
} // unbind
|
|
|
|
|
|
|
|
} // Shader
|
|
|
|
|
|
|
|
struct Texture {
|
|
|
|
|
|
|
|
import derelict.sdl2.sdl;
|
|
|
|
import derelict.sdl2.image;
|
|
|
|
|
|
|
|
private {
|
|
|
|
|
|
|
|
GLuint texture_; //OpenGL handle for texture
|
|
|
|
GLenum input_format_, output_format_, data_type_;
|
|
|
|
int width_, height_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@property @nogc nothrow {
|
|
|
|
|
|
|
|
int width() const { return width_; }
|
|
|
|
int height() const { return height_; }
|
|
|
|
GLuint handle() { return texture_; }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@disable this(this);
|
|
|
|
|
|
|
|
nothrow @nogc
|
|
|
|
this(int width, int height, GLenum input_format, GLenum output_format, GLenum unpack_alignment) {
|
|
|
|
|
|
|
|
width_ = width;
|
|
|
|
height_ = height;
|
|
|
|
input_format_ = input_format;
|
|
|
|
output_format_ = output_format;
|
|
|
|
data_type_ = GL_UNSIGNED_BYTE;
|
|
|
|
|
|
|
|
glGenTextures(1, &texture_);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
|
|
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, GL_UNSIGNED_BYTE, cast(void*)0);
|
|
|
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
|
|
|
|
} //this
|
|
|
|
|
|
|
|
nothrow @nogc
|
|
|
|
void create(void* pixels, int width, int height, GLenum input_format = GL_RGB, GLenum output_format = GL_RGB, GLenum data_type = GL_UNSIGNED_BYTE) {
|
|
|
|
|
|
|
|
width_ = width;
|
|
|
|
height_ = height;
|
|
|
|
input_format_ = input_format;
|
|
|
|
output_format_ = output_format;
|
|
|
|
data_type_ = data_type;
|
|
|
|
|
|
|
|
//generate single texture, put handle in texture
|
|
|
|
glGenTextures(1, &texture_);
|
|
|
|
|
|
|
|
//normal 2d texture, bind to our texture handle
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
|
|
|
|
|
|
|
//set texture parameters in currently bound texture, controls texture wrapping (or GL_CLAMP?)
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
|
|
|
|
//linearly interpolate between pixels, MIN if texture is too small for drawing area, MAG if drawing area is smaller than texture
|
2018-09-30 18:06:30 +01:00
|
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
2018-09-30 17:22:59 +01:00
|
|
|
|
|
|
|
//texture type, level, format to store as, width, height, border, format loaded in
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, input_format_, width_, height_, 0, output_format_, data_type_, pixels);
|
|
|
|
|
|
|
|
//UNBIND
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
|
|
|
|
} // this
|
|
|
|
|
|
|
|
~this() nothrow @nogc {
|
|
|
|
|
|
|
|
if (texture_ != 0) {
|
|
|
|
glDeleteTextures(1, &texture_);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // ~this
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Binds the texture handle, takes an argument for which texture unit to use.
|
|
|
|
*/
|
|
|
|
nothrow @nogc
|
|
|
|
void bind(int unit) {
|
|
|
|
|
|
|
|
assert(unit >= 0 && unit <= 31);
|
|
|
|
glActiveTexture(GL_TEXTURE0 + unit); //since this is sequential, this works
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
|
|
|
|
|
|
|
} // bind
|
|
|
|
|
|
|
|
nothrow @nogc
|
|
|
|
void unbind() {
|
|
|
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
|
|
|
|
} // unbind
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the texture in place given the new texture buffer.
|
|
|
|
* Takes an optional offset to update only a part of the texture.
|
|
|
|
**/
|
|
|
|
nothrow @nogc
|
|
|
|
void update(void[] pixels, size_t offset = 0) {
|
|
|
|
|
|
|
|
bind(0);
|
2018-09-30 18:06:30 +01:00
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, input_format_, data_type_, pixels.ptr);
|
2018-09-30 17:22:59 +01:00
|
|
|
unbind();
|
|
|
|
|
|
|
|
} // update
|
|
|
|
|
|
|
|
} // Texture
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generic VertexArray structure, used to upload data of any given vertex type to the GPU.
|
|
|
|
*/
|
|
|
|
struct VertexArray {
|
|
|
|
|
|
|
|
private {
|
|
|
|
|
|
|
|
GLuint vao_;
|
|
|
|
GLuint vbo_;
|
|
|
|
GLenum type_; // type of vertex data, GL_TRIANGLES etc
|
|
|
|
uint num_vertices_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@disable this(this);
|
|
|
|
|
|
|
|
nothrow @nogc
|
|
|
|
void create(in float[2][] vertices, GLenum draw_type = GL_STATIC_DRAW, GLenum primitive = GL_TRIANGLES) {
|
|
|
|
|
|
|
|
this.num_vertices_ = cast(uint)vertices.length;
|
|
|
|
this.type_ = primitive;
|
|
|
|
|
|
|
|
glGenVertexArrays(1, &vao_);
|
|
|
|
glBindVertexArray(vao_);
|
|
|
|
|
|
|
|
glGenBuffers(1, &vbo_);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, vertices.length * vertices[0].sizeof, vertices.ptr, draw_type);
|
|
|
|
|
2018-09-30 18:06:30 +01:00
|
|
|
// pos
|
2018-09-30 17:22:59 +01:00
|
|
|
glEnableVertexAttribArray(0);
|
|
|
|
glVertexAttribPointer(0,
|
2018-09-30 18:06:30 +01:00
|
|
|
2,
|
2018-09-30 17:22:59 +01:00
|
|
|
GL_FLOAT,
|
|
|
|
GL_FALSE,
|
2018-09-30 18:06:30 +01:00
|
|
|
float.sizeof * 2,
|
|
|
|
null
|
2018-09-30 17:22:59 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
glBindVertexArray(0);
|
|
|
|
|
|
|
|
} // this
|
|
|
|
|
|
|
|
~this() nothrow @nogc {
|
|
|
|
if (vao_ != 0) {
|
|
|
|
glDeleteVertexArrays(1, &vao_);
|
|
|
|
}
|
|
|
|
} // ~this
|
|
|
|
|
|
|
|
void bind() nothrow @nogc {
|
|
|
|
glBindVertexArray(vao_);
|
|
|
|
} // bind
|
|
|
|
|
|
|
|
void draw() nothrow @nogc {
|
|
|
|
glDrawArrays(type_, 0, num_vertices_);
|
|
|
|
} // draw
|
|
|
|
|
|
|
|
void unbind() nothrow @nogc {
|
|
|
|
glBindVertexArray(0);
|
|
|
|
} // unbind
|
|
|
|
|
|
|
|
} // VertexArray
|
|
|
|
|
|
|
|
const char* vs_shader = q{
|
|
|
|
#version 330 core
|
|
|
|
|
|
|
|
uniform vec2 screen_size;
|
|
|
|
|
|
|
|
layout(location = 0) in vec2 pos;
|
|
|
|
|
|
|
|
out vec2 frag_uv;
|
|
|
|
|
|
|
|
void main() {
|
2018-09-30 18:06:30 +01:00
|
|
|
gl_Position = vec4(pos, 0.0, 1.0);
|
|
|
|
frag_uv = clamp(pos, vec2(0.0, 0.0), vec2(1.0, 1.0));
|
2018-09-30 17:22:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const char* fs_shader = q{
|
|
|
|
#version 330 core
|
|
|
|
|
|
|
|
uniform sampler2D tex;
|
|
|
|
|
|
|
|
in vec2 frag_uv;
|
|
|
|
|
|
|
|
out vec4 out_col;
|
|
|
|
|
|
|
|
void main() {
|
2018-09-30 18:58:46 +01:00
|
|
|
float v = texture(tex, frag_uv.st).r * 255;
|
2018-09-30 18:06:30 +01:00
|
|
|
out_col = vec4(v, v, v, 1.0);
|
2018-09-30 17:22:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
Shader shader;
|
|
|
|
VertexArray vao;
|
|
|
|
Texture tex;
|
|
|
|
|
|
|
|
void create(void* pixels, int w, int h) {
|
|
|
|
|
|
|
|
float[2][6] rect = [
|
2018-09-30 18:06:30 +01:00
|
|
|
[-1.0f, -1.0f], // top left
|
|
|
|
[1.0f, -1.0f], // top right
|
2018-09-30 17:22:59 +01:00
|
|
|
[1.0f, 1.0f], // bottom right
|
|
|
|
|
2018-09-30 18:06:30 +01:00
|
|
|
[-1.0f, -1.0f], // top left
|
|
|
|
[-1.0f, 1.0f], // bottom left
|
2018-09-30 17:22:59 +01:00
|
|
|
[1.0f, 1.0f], // bottom right
|
|
|
|
];
|
|
|
|
|
|
|
|
vao.create(rect[]);
|
2018-09-30 18:06:30 +01:00
|
|
|
tex.create(pixels, w, h, GL_RED, GL_RED);
|
2018-09-30 17:22:59 +01:00
|
|
|
shader.compile(&vs_shader, &fs_shader);
|
|
|
|
|
|
|
|
} // create
|
|
|
|
|
|
|
|
void draw(int screen_w, int screen_h) {
|
|
|
|
|
|
|
|
vao.bind();
|
|
|
|
shader.bind();
|
|
|
|
shader.setUniforms(screen_w, screen_h);
|
|
|
|
tex.bind(0);
|
|
|
|
vao.draw();
|
|
|
|
|
|
|
|
} // draw
|
|
|
|
|
2018-09-30 18:06:30 +01:00
|
|
|
nothrow @nogc
|
|
|
|
void update(void[] pixels, size_t offset = 0) {
|
|
|
|
tex.update(pixels, offset);
|
|
|
|
} // update
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
} // PixelBuffer
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
struct Chip8Status {
|
|
|
|
|
|
|
|
//emu ptr
|
|
|
|
Emulator* run_;
|
|
|
|
Chip8* emu_;
|
|
|
|
|
|
|
|
// mem editor
|
|
|
|
// MemoryEditor mem_editor_;
|
|
|
|
|
|
|
|
// state
|
|
|
|
bool status_menu_ = true;
|
|
|
|
int stack_cur_ = -1;
|
|
|
|
|
|
|
|
void initialize(Emulator* run, Chip8* emu) {
|
|
|
|
this.run_ = run;
|
|
|
|
this.emu_ = emu;
|
|
|
|
} // initialize
|
|
|
|
|
|
|
|
alias Callback = float delegate(int idx, const char** out_text);
|
|
|
|
|
|
|
|
static extern(C) bool doCallback(void* ptr, int idx, const (char**) out_text) {
|
|
|
|
|
|
|
|
auto callback = *(cast(Callback*) ptr);
|
|
|
|
callback(idx, out_text);
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} // doCallback;
|
|
|
|
|
|
|
|
void getStackFrame(int idx, const (char**) out_text) {
|
|
|
|
|
|
|
|
static char[32] frame_text;
|
|
|
|
|
|
|
|
import core.stdc.stdio : sprintf;
|
|
|
|
sprintf(frame_text.ptr, "0x%04X", emu_.stack[idx]);
|
|
|
|
|
|
|
|
auto p = frame_text.ptr;
|
|
|
|
auto op = cast(char**)out_text;
|
|
|
|
*op = p;
|
|
|
|
|
|
|
|
} // getStackFrame
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
void resetShortcut() {
|
|
|
|
|
|
|
|
emu_.reset();
|
|
|
|
|
|
|
|
} // resetShortcut
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
void loadShortcut() {
|
|
|
|
|
|
|
|
import std.file : read;
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
auto buf = read("programs/sqrt.ch8");
|
2018-06-23 01:02:07 +01:00
|
|
|
emu_.load(0x200, buf); // do ze load yes, will copy all the data in
|
|
|
|
|
|
|
|
} // loadShortcut
|
|
|
|
|
|
|
|
void saveShortcut() {
|
|
|
|
|
|
|
|
} // saveShortcut
|
|
|
|
|
|
|
|
void debugShortcut() {
|
|
|
|
status_menu_ = !status_menu_;
|
|
|
|
} // debugShortcut
|
|
|
|
|
|
|
|
void redrawShortcut() {
|
|
|
|
emu_.draw_flag = true;
|
|
|
|
} // redrawShortcut
|
|
|
|
|
|
|
|
void toggleRunShortcut() {
|
|
|
|
emu_.run_flag = !emu_.run_flag;
|
|
|
|
} // toggleRunShortcut
|
|
|
|
|
|
|
|
void stepShortcut() {
|
|
|
|
emu_.step();
|
|
|
|
} // stepShortcut
|
|
|
|
|
|
|
|
void quitShortcut() {
|
|
|
|
run_.quit();
|
|
|
|
} // quitShortcut
|
|
|
|
|
|
|
|
void draw() {
|
|
|
|
|
|
|
|
if (!status_menu_) return;
|
|
|
|
|
|
|
|
if (igBeginMainMenuBar()) {
|
|
|
|
|
|
|
|
if (igBeginMenu("Menu")) {
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
if (igMenuItem("Reset", "CTRL+R")) {
|
|
|
|
resetShortcut();
|
2018-06-23 01:02:07 +01:00
|
|
|
}
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
if (igMenuItem("Load", "CTRL+L")) {
|
|
|
|
loadShortcut();
|
2018-06-23 01:02:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (igMenuItem("Debug", "CTRL+D")) {
|
|
|
|
debugShortcut();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (igMenuItem("Quit", "CTRL+Q")) {
|
|
|
|
quitShortcut();
|
|
|
|
}
|
|
|
|
|
|
|
|
igEndMenu();
|
|
|
|
}
|
|
|
|
|
|
|
|
igEndMainMenuBar();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
import std.range : chunks;
|
|
|
|
|
|
|
|
igBegin("Emulator Status");
|
|
|
|
|
|
|
|
igBeginChild("General");
|
|
|
|
igText("OpCode: 0x%04X", emu_.cpu.opcode);
|
|
|
|
igText("PC:");
|
|
|
|
igSameLine();
|
2018-09-30 21:25:40 +01:00
|
|
|
igText("pc: 0x%04X (%hu)", emu_.cpu.pc, emu_.cpu.pc);
|
|
|
|
// igDragInt("##pc", cast(int*)&emu_.cpu.pc, 0.5f, 0, emu_.ram.length);
|
2018-06-23 01:02:07 +01:00
|
|
|
|
|
|
|
igText("Registers (v0 - vF)");
|
|
|
|
igColumns(4, null, false);
|
|
|
|
|
|
|
|
auto n = 0;
|
|
|
|
foreach (ref chunk; emu_.cpu.v[].chunks(4)) {
|
|
|
|
igText("v%0X 0x%02X ", n, chunk[0]);
|
|
|
|
igText("v%0X 0x%02X ", n+1, chunk[1]);
|
|
|
|
igText("v%0X 0x%02X ", n+2, chunk[2]);
|
|
|
|
igText("v%0X 0x%02X ", n+3, chunk[3]);
|
|
|
|
igNextColumn();
|
|
|
|
n += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
igColumns(1, null, false);
|
|
|
|
|
|
|
|
igText("Index Register: 0x%04X", emu_.cpu.i);
|
|
|
|
igText("Delay Timer: 0x%04X", emu_.cpu.delay_timer);
|
|
|
|
igText("Sound Timer: 0x%04X", emu_.cpu.sound_timer);
|
|
|
|
|
|
|
|
if(igButton("Step")) {
|
|
|
|
emu_.step();
|
|
|
|
}
|
|
|
|
|
|
|
|
igSameLine();
|
|
|
|
|
|
|
|
if (igButton((emu_.run_flag) ? "Stop" : "Run")) {
|
|
|
|
emu_.run_flag = !emu_.run_flag;
|
|
|
|
}
|
|
|
|
|
|
|
|
igText("Stack");
|
|
|
|
auto d = &getStackFrame;
|
|
|
|
igPushItemWidth(-1);
|
|
|
|
igListBox2("", &stack_cur_, &doCallback, cast(void*)&d, emu_.stack.length, 16);
|
|
|
|
igPopItemWidth();
|
|
|
|
|
|
|
|
igEndChild();
|
|
|
|
igEnd();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// mem_editor_.draw("Emulator Memory", emu_.ram[]);
|
|
|
|
|
|
|
|
} // draw
|
|
|
|
|
2018-06-23 01:15:25 +01:00
|
|
|
void handleEvent(ref SDL_Event ev) {
|
|
|
|
switch (ev.type) with (SDL_EventType) {
|
|
|
|
case SDL_KEYDOWN:
|
|
|
|
if ((ev.key.keysym.mod & KMOD_CTRL) != 0) {
|
|
|
|
switch (ev.key.keysym.scancode) with (SDL_EventType) {
|
2018-09-30 17:22:59 +01:00
|
|
|
case SDL_SCANCODE_R: resetShortcut(); break;
|
2018-06-23 01:15:25 +01:00
|
|
|
case SDL_SCANCODE_L: loadShortcut(); break;
|
|
|
|
case SDL_SCANCODE_D: debugShortcut(); break;
|
|
|
|
case SDL_SCANCODE_G: redrawShortcut(); break;
|
2018-09-30 17:22:59 +01:00
|
|
|
case SDL_SCANCODE_T: toggleRunShortcut(); break;
|
|
|
|
case SDL_SCANCODE_S: stepShortcut(); break;
|
2018-09-30 20:12:37 +01:00
|
|
|
case SDL_SCANCODE_Q: quitShortcut(); break;
|
2018-06-23 01:15:25 +01:00
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
|
|
|
|
quitShortcut();
|
|
|
|
}
|
|
|
|
}
|
2018-09-30 20:12:37 +01:00
|
|
|
break;
|
2018-06-23 01:15:25 +01:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} // handleEvent
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
} // Chip8Status
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
pure
|
|
|
|
@property {
|
|
|
|
ubyte x(ref ushort oc) {
|
|
|
|
return (oc & 0x0F00) >> 8;
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
ubyte y(ref ushort oc) {
|
|
|
|
return (oc & 0x00F0) >> 4;
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
ubyte n(ref ushort oc) {
|
|
|
|
return oc & 0x000F;
|
|
|
|
}
|
2018-06-22 20:51:16 +01:00
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
ubyte nn(ref ushort oc) {
|
|
|
|
return (oc & 0x00FF);
|
|
|
|
}
|
2018-06-22 20:51:16 +01:00
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
ushort nnn(ref ushort oc) {
|
|
|
|
return (oc & 0x0FFF);
|
|
|
|
}
|
|
|
|
}
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
struct Chip8 {
|
|
|
|
|
|
|
|
alias OpCode = ushort;
|
|
|
|
alias ProgramCounter = ushort;
|
|
|
|
alias Memory = ubyte[4096];
|
|
|
|
alias Stack = ushort[16];
|
|
|
|
|
|
|
|
alias Register = ubyte;
|
|
|
|
alias Registers = Register[16];
|
|
|
|
alias IndexRegister = ushort;
|
|
|
|
|
|
|
|
alias KeyPad = ubyte[16];
|
|
|
|
|
|
|
|
struct CPU {
|
|
|
|
|
|
|
|
OpCode opcode = 0; //current opcode
|
|
|
|
ProgramCounter pc = 0x200;
|
|
|
|
|
|
|
|
Registers v; //16 general purpose registers
|
|
|
|
IndexRegister i;
|
|
|
|
|
|
|
|
Register delay_timer;
|
|
|
|
Register sound_timer;
|
|
|
|
|
|
|
|
} // CPU
|
|
|
|
|
|
|
|
CPU cpu;
|
|
|
|
Memory ram;
|
|
|
|
|
|
|
|
Stack stack;
|
|
|
|
ubyte sp;
|
|
|
|
|
|
|
|
KeyPad kp;
|
|
|
|
|
|
|
|
ubyte[64*32] screen_buf;
|
|
|
|
ubyte[3][64*32] screen_data;
|
|
|
|
|
|
|
|
bool run_flag;
|
|
|
|
bool draw_flag;
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
void load(size_t offset, in void[] data) {
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
assert(offset + data.length < ram.length);
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
ram[offset .. offset + data.length] = cast(ubyte[])data[];
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
} // load
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
void reset() {
|
|
|
|
|
|
|
|
cpu = cpu.init;
|
2018-09-30 18:58:46 +01:00
|
|
|
stack = stack.init;
|
2018-09-30 20:12:37 +01:00
|
|
|
ram[0x200 .. $ - 1] = 0;
|
|
|
|
cpu.pc = 0x200;
|
2018-09-30 18:58:46 +01:00
|
|
|
sp = sp.init;
|
|
|
|
|
|
|
|
run_flag = run_flag.init;
|
|
|
|
draw_flag = draw_flag.init;
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
screen_buf = screen_buf.init;
|
|
|
|
screen_data = screen_data.init;
|
|
|
|
|
|
|
|
} // reset
|
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
void step() {
|
|
|
|
|
|
|
|
cpu.opcode = ram[cpu.pc] << 8 | ram[cpu.pc + 1];
|
2018-09-30 21:25:40 +01:00
|
|
|
ushort pc_target = cast(ProgramCounter)(cpu.pc + 2u);
|
2018-06-22 20:51:16 +01:00
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
// writefln("opcode: 0x%X, pc: 0x%X : %d", cpu.opcode, cpu.pc, cpu.pc);
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
switch (cpu.opcode & 0xF000) with (cpu) {
|
|
|
|
|
|
|
|
case 0x0000:
|
|
|
|
|
|
|
|
switch (cpu.opcode & 0x0FFF) {
|
|
|
|
|
|
|
|
case 0x00E0: // 0x00E0 Clears the screen.
|
2018-09-30 18:06:30 +01:00
|
|
|
screen_buf[0..$] = 0;
|
2018-06-22 20:51:16 +01:00
|
|
|
draw_flag = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x00EE: // 0x00EE Returns from a subroutine.
|
|
|
|
pc_target = stack[--sp];
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: // 0x0NNN Calls RCA 1802 program at address NNN. Not necessary for most ROMs.
|
|
|
|
//assert(0, "0x0NNN RCA 1802 program opcode not implemented!");
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1000: // 0x1NNN Jumps to address NNN.
|
2018-09-30 20:42:26 +01:00
|
|
|
pc_target = cpu.opcode.nnn;
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2000: // 0x2NNN Calls subroutine at NNN.
|
|
|
|
|
|
|
|
stack[sp++] = cpu.pc;
|
2018-09-30 20:42:26 +01:00
|
|
|
pc_target = cpu.opcode.nnn;
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN.
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
if (cpu.v[cpu.opcode.x] == (cpu.opcode.nn)) {
|
2018-09-30 21:25:40 +01:00
|
|
|
pc_target += 2u;
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN.
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
if (cpu.v[cpu.opcode.x] != (cpu.opcode.nn)) {
|
2018-09-30 21:25:40 +01:00
|
|
|
pc_target += 2u;
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY.
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
if (cpu.v[cpu.opcode.x] == cpu.v[cpu.opcode.y]) {
|
2018-09-30 21:25:40 +01:00
|
|
|
pc_target += 2u;
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x6000: // 0x6XNN Sets VX to NN.
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.v[cpu.opcode.x] = cpu.opcode.nn;
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x7000: // 0x7XNN Adds NN to VX.
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.v[cpu.opcode.x] += cpu.opcode.nn;
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x8000:
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte x = cpu.opcode.x;
|
|
|
|
ubyte y = cpu.opcode.y;
|
2018-06-22 20:51:16 +01:00
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
switch (cpu.opcode.n) {
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
case 0x0000: // 0x8XY0 Sets VX to the value of VY.
|
|
|
|
cpu.v[x] = cpu.v[y];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0001: // 0x8XY1 Sets VX to VX or VY.
|
|
|
|
cpu.v[x] = cpu.v[x] | cpu.v[y];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0002: // 0x8XY2 Sets VX to VX and VY.
|
|
|
|
cpu.v[x] = cpu.v[x] & cpu.v[y];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0003: // 0x8XY3 Sets VX to VX xor VY.
|
|
|
|
cpu.v[x] = cpu.v[x] ^ cpu.v[y];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't.
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte vx = cpu.v[x];
|
|
|
|
ubyte vy = cpu.v[y];
|
2018-09-30 18:58:46 +01:00
|
|
|
if (cast(ushort)vx + cast(ushort)vy > 255) {
|
|
|
|
cpu.v[0xF] = 1;
|
|
|
|
} else {
|
|
|
|
cpu.v[0xF] = 0;
|
|
|
|
}
|
|
|
|
cpu.v[x] += cpu.v[y];
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte vx = cpu.v[x];
|
|
|
|
ubyte vy = cpu.v[y];
|
2018-09-30 18:58:46 +01:00
|
|
|
if (vx > vy) {
|
|
|
|
cpu.v[0xF] = 1;
|
|
|
|
} else {
|
|
|
|
cpu.v[0xF] = 0;
|
|
|
|
}
|
|
|
|
cpu.v[x] -= cpu.v[y];
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift.
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte vx = cpu.v[x];
|
|
|
|
cpu.v[0xF] = vx & 0x1;
|
2018-06-22 20:51:16 +01:00
|
|
|
cpu.v[x] >>= 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte vx = cpu.v[x];
|
|
|
|
ubyte vy = cpu.v[y];
|
2018-09-30 18:58:46 +01:00
|
|
|
if (vy > vx) {
|
|
|
|
cpu.v[0xF] = 1;
|
|
|
|
} else {
|
|
|
|
cpu.v[0xF] = 0;
|
|
|
|
}
|
2018-09-30 21:25:40 +01:00
|
|
|
cpu.v[x] = cast(Register)(vy - vx);
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift.
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte vx = cpu.v[x];
|
|
|
|
cpu.v[0xF] = vx >> 7;
|
2018-06-22 20:51:16 +01:00
|
|
|
cpu.v[x] <<= 1;
|
|
|
|
break;
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
default: // unhandled for some reason
|
2018-06-22 20:51:16 +01:00
|
|
|
writefln("unknown opcode: 0x%x", cpu.opcode);
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY.
|
|
|
|
|
2018-09-30 20:42:26 +01:00
|
|
|
if (cpu.v[cpu.opcode.x] != cpu.v[cpu.opcode.y]) {
|
2018-09-30 21:25:40 +01:00
|
|
|
pc_target += 2u; // do skip yes
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xA000: // 0xANNN Sets I to the address NNN.
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.i = cpu.opcode.nnn;
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xB000: // 0xBNNN Jumps to the address NNN plus V0.
|
2018-09-30 20:42:26 +01:00
|
|
|
pc_target = cast(ProgramCounter)(cpu.opcode.nnn + cpu.v[0x0]);
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN.
|
|
|
|
|
|
|
|
import std.random : uniform;
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte x = cpu.opcode.x;
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.v[x] = uniform(Register.min, Register.max) & (cpu.opcode & 0x00FF);
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
// 0xDXYN
|
|
|
|
// Sprites stored in memory at location in index register (I), 8bits wide.
|
|
|
|
// Wraps around the screen. If when drawn, clears a pixel, register VF is set to 1 otherwise it is zero.
|
|
|
|
// All drawing is XOR drawing (i.e. it toggles the screen pixels).
|
|
|
|
// Sprites are drawn starting at position VX, VY. N is the number of 8bit rows that need to be drawn.
|
|
|
|
// If N is greater than 1, second line continues at position VX, VY+1, and so on.
|
|
|
|
case 0xD000:
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
ProgramCounter spr_addr = cpu.i;
|
|
|
|
ubyte x = cpu.opcode.x;
|
|
|
|
ubyte y = cpu.opcode.y;
|
|
|
|
ubyte n = cpu.opcode.n;
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
foreach(int row; 0 .. n) {
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
ushort pixel = ram[spr_addr + row];
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
foreach (int col; 0 .. 8) {
|
2018-09-30 21:25:40 +01:00
|
|
|
if ((pixel & (0x80 >> col)) != 0) {
|
|
|
|
ubyte x_off = cast(ubyte)((x + col) % 64);
|
|
|
|
ubyte y_off = cast(ubyte)((y + row) % 32);
|
|
|
|
ushort offset = x_off + (y_off * 64);
|
2018-09-30 20:12:37 +01:00
|
|
|
if (screen_buf[offset] == 1) {
|
2018-06-22 20:51:16 +01:00
|
|
|
cpu.v[0xF] = 1;
|
|
|
|
}
|
2018-09-30 20:12:37 +01:00
|
|
|
screen_buf[offset] ^= 1;
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
draw_flag = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xE000:
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte x = cpu.opcode.x;
|
|
|
|
ubyte key = cpu.v[x];
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
switch (cpu.opcode & 0x000F) {
|
|
|
|
|
|
|
|
case 0x000E: // 0xEX9E Skips the next instruction if the key stored in VX is pressed.
|
|
|
|
writefln("0xEXA1: skip instruction if VX not pressed: %x", key);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed.
|
|
|
|
writefln("0xEXA1: skip instruction if VX not pressed: %x", key);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: //unhandled for some reason
|
|
|
|
writefln("unknown opcode: 0x%x", cpu.opcode);
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xF000:
|
|
|
|
|
|
|
|
switch (cpu.opcode & 0x00FF) {
|
|
|
|
|
|
|
|
case 0x0007: // 0xFX07 Sets VX to the value of the delay timer.
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.v[cpu.opcode.x] = cpu.delay_timer;
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX.
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0015: // 0xFX15 Sets the delay timer to VX.
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.delay_timer = cpu.v[cpu.opcode.x];
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0018: // 0xFX18 Sets the sound timer to VX.
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.sound_timer = cpu.v[cpu.opcode.x];
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x001E: // 0xFX1E Adds VX to I.
|
2018-09-30 21:25:40 +01:00
|
|
|
if (cpu.i + cpu.v[cpu.opcode.x] > 0xFF) {
|
|
|
|
cpu.v[0xF] = 1;
|
|
|
|
} else {
|
|
|
|
cpu.v[0xF] = 0;
|
|
|
|
}
|
2018-09-30 20:42:26 +01:00
|
|
|
cpu.i += cpu.v[cpu.opcode.x];
|
2018-06-22 20:51:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX.
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte vx = cpu.v[cpu.opcode.x];
|
|
|
|
// ushort char_addr = 0x200 + (vx * 40); // base of char sprites + value of vx * bits per character
|
|
|
|
ushort char_addr = vx * 0x5;
|
2018-06-22 20:51:16 +01:00
|
|
|
cpu.i = char_addr;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// 0xFX33 Stores the Binary-coded decimal representation of VX,
|
|
|
|
// with the most significant of three digits at the address in I,
|
|
|
|
// the middle digit at I plus 1, and the least significant digit at I plus 2.
|
|
|
|
case 0x0033:
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
ubyte vx = cpu.v[cpu.opcode.x];
|
2018-06-22 20:51:16 +01:00
|
|
|
ram[cpu.i] = vx / 100;
|
|
|
|
ram[cpu.i + 1] = (vx / 10) % 10;
|
|
|
|
ram[cpu.i + 2] = (vx % 100) % 10;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0055: // 0xFX55 Stores V0 to VX in memory starting at address I.
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
IndexRegister addr = cpu.i;
|
2018-06-22 20:51:16 +01:00
|
|
|
foreach (reg; cpu.v) {
|
|
|
|
ram[addr++] = reg;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I.
|
|
|
|
|
2018-09-30 21:25:40 +01:00
|
|
|
IndexRegister addr = cpu.i;
|
2018-06-22 20:51:16 +01:00
|
|
|
foreach (ref reg; cpu.v) {
|
|
|
|
reg = ram[addr++];
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2018-09-30 18:58:46 +01:00
|
|
|
default: // unhandled for some reason
|
2018-06-22 20:51:16 +01:00
|
|
|
writefln("unknown opcode: 0x%x", cpu.opcode);
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
writefln("unknown opcode: 0x%x", cpu.opcode);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* now update pc and timer registers. */
|
|
|
|
|
|
|
|
cpu.pc = pc_target;
|
|
|
|
|
|
|
|
if (cpu.delay_timer != 0u) {
|
|
|
|
--cpu.delay_timer;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cpu.sound_timer != 0u) {
|
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
if (cpu.sound_timer == 1u) {
|
2018-06-22 20:51:16 +01:00
|
|
|
writefln("beep!");
|
|
|
|
}
|
|
|
|
|
|
|
|
--cpu.sound_timer;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} // step
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
void handleEvent(ref SDL_Event ev) {
|
2018-06-22 23:54:39 +01:00
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
} // handleEvent
|
2018-06-22 23:54:39 +01:00
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
void tick() {
|
|
|
|
|
|
|
|
if (run_flag) {
|
|
|
|
step();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // tick
|
|
|
|
|
|
|
|
} // Emulator
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
void loadLibs() {
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
DerelictSDL2.load();
|
2018-06-22 23:54:39 +01:00
|
|
|
DerelictImgui.load();
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
void initLibs() {
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
SDL_Init(SDL_INIT_VIDEO);
|
2018-06-23 01:02:07 +01:00
|
|
|
SDL_GL_LoadLibrary(null);
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
void setupImgui() {
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
loadLibs();
|
|
|
|
initLibs();
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
Emulator emu;
|
|
|
|
emu.create();
|
|
|
|
emu.run();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Emulator {
|
|
|
|
|
|
|
|
bool running;
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
// debug
|
|
|
|
Chip8Status status;
|
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
Window window;
|
2018-06-23 01:02:07 +01:00
|
|
|
Imgui imgui;
|
|
|
|
Chip8 emu;
|
2018-06-22 20:51:16 +01:00
|
|
|
|
2018-09-30 17:22:59 +01:00
|
|
|
// drawing
|
|
|
|
PixelBuffer buf;
|
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
void create() {
|
|
|
|
|
|
|
|
// create window
|
2018-06-23 01:15:25 +01:00
|
|
|
window.createWindow(960, 768);
|
2018-06-23 01:02:07 +01:00
|
|
|
|
|
|
|
// setup imgui
|
|
|
|
imgui.initialize();
|
|
|
|
imgui.createDeviceObjects();
|
|
|
|
|
|
|
|
// setup debug ui
|
|
|
|
status.initialize(&this, &emu);
|
2018-06-22 20:51:16 +01:00
|
|
|
|
2018-09-30 18:06:30 +01:00
|
|
|
// debug data
|
2018-09-30 18:58:46 +01:00
|
|
|
emu.screen_buf[0] = 1;
|
|
|
|
emu.screen_buf[64 - 1] = 1;
|
|
|
|
emu.screen_buf[64*32 - 64] = 1;
|
|
|
|
emu.screen_buf[64*32 - 1] = 1;
|
2018-09-30 17:22:59 +01:00
|
|
|
|
2018-09-30 20:12:37 +01:00
|
|
|
// set up pixel buffer to poke at
|
2018-09-30 18:06:30 +01:00
|
|
|
buf.create(emu.screen_buf.ptr, 64, 32);
|
2018-09-30 17:22:59 +01:00
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
void handleEvents() {
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
SDL_Event event;
|
|
|
|
while (SDL_PollEvent(&event)) {
|
2018-06-22 23:54:39 +01:00
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
imgui.handleEvent(event);
|
2018-06-23 01:15:25 +01:00
|
|
|
status.handleEvent(event);
|
2018-06-23 01:02:07 +01:00
|
|
|
emu.handleEvent(event);
|
2018-06-22 23:54:39 +01:00
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
switch (event.type) with (SDL_EventType) {
|
|
|
|
case SDL_QUIT: {
|
|
|
|
running = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-06-22 23:54:39 +01:00
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
} // handleEvents
|
2018-06-22 20:51:16 +01:00
|
|
|
|
|
|
|
void run() {
|
|
|
|
|
|
|
|
running = true;
|
|
|
|
|
|
|
|
while (running) {
|
2018-06-23 01:02:07 +01:00
|
|
|
handleEvents();
|
2018-06-22 20:51:16 +01:00
|
|
|
tick();
|
|
|
|
draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void tick() {
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
emu.tick();
|
|
|
|
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void draw() {
|
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
window.renderClear(0x428bca);
|
2018-09-30 17:22:59 +01:00
|
|
|
|
|
|
|
int w, h;
|
|
|
|
window.windowSize(w, h);
|
2018-09-30 18:06:30 +01:00
|
|
|
if (emu.draw_flag) {
|
|
|
|
buf.update(emu.screen_buf);
|
2018-09-30 20:12:37 +01:00
|
|
|
emu.draw_flag = false;
|
2018-09-30 18:06:30 +01:00
|
|
|
}
|
2018-09-30 17:22:59 +01:00
|
|
|
buf.draw(w, h);
|
2018-09-30 18:58:46 +01:00
|
|
|
|
2018-06-23 01:02:07 +01:00
|
|
|
imgui.newFrame(window);
|
|
|
|
|
|
|
|
status.draw();
|
|
|
|
|
|
|
|
imgui.endFrame();
|
|
|
|
window.renderPresent();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void quit() {
|
|
|
|
running = false;
|
2018-06-22 20:51:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|