import std.stdio; import derelict.sdl2.sdl; import derelict.imgui.imgui; import glad.gl.all; import window; import imgui; 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 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); //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); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, input_format_, data_type_, pixels.ptr); 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); // pos glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, float.sizeof * 2, null ); 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() { gl_Position = vec4(pos, 0.0, 1.0); frag_uv = clamp(pos, vec2(0.0, 0.0), vec2(1.0, 1.0)); } }; const char* fs_shader = q{ #version 330 core uniform sampler2D tex; in vec2 frag_uv; out vec4 out_col; void main() { float v = texture(tex, frag_uv.st).r * 255; out_col = vec4(v, v, v, 1.0); } }; Shader shader; VertexArray vao; Texture tex; void create(void* pixels, int w, int h) { float[2][6] rect = [ [-1.0f, -1.0f], // top left [1.0f, -1.0f], // top right [1.0f, 1.0f], // bottom right [-1.0f, -1.0f], // top left [-1.0f, 1.0f], // bottom left [1.0f, 1.0f], // bottom right ]; vao.create(rect[]); tex.create(pixels, w, h, GL_RED, GL_RED); 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 nothrow @nogc void update(void[] pixels, size_t offset = 0) { tex.update(pixels, offset); } // update } // PixelBuffer 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 void resetShortcut() { emu_.reset(); } // resetShortcut void loadShortcut() { import std.file : read; auto buf = read("programs/chip8_picture.ch8"); 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")) { if (igMenuItem("Reset", "CTRL+R")) { resetShortcut(); } if (igMenuItem("Load", "CTRL+L")) { loadShortcut(); } 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(); igDragInt("##pc", cast(int*)&emu_.cpu.pc, 0.5f, 0, emu_.ram.length); 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 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) { case SDL_SCANCODE_R: resetShortcut(); break; case SDL_SCANCODE_L: loadShortcut(); break; case SDL_SCANCODE_D: debugShortcut(); break; case SDL_SCANCODE_G: redrawShortcut(); break; case SDL_SCANCODE_T: toggleRunShortcut(); break; case SDL_SCANCODE_S: stepShortcut(); break; case SDL_SCANCODE_Q: quitShortcut(); break; default: break; } } else { if (ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { quitShortcut(); } } break; default: break; } } // handleEvent } // Chip8Status // this is a collosally awful solution, but if it works :D string doCapture(string sym, uint start, uint end)(){ import std.string : format; enum bits = end - start; static if (bits > 16) { alias RT = uint; } else static if (bits > 8) { alias RT = ushort; } else { alias RT = ubyte; } auto str = "0b"; foreach (i; 0 .. end) { if (i >= start) str ~= "1"; else str ~= "0"; } return format(q{ %s result = ((%s & %s) >> %d); return result; }, RT.stringof, sym, str, start); } // doCapture auto capture(uint start, uint end)(ushort op) { enum str = doCapture!(op.stringof, start, end)(); mixin(str); } // capture 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; void load(size_t offset, in void[] data) { assert(offset + data.length < ram.length); ram[offset .. offset + data.length] = cast(ubyte[])data[]; } // load void reset() { cpu = cpu.init; stack = stack.init; ram[0x200 .. $ - 1] = 0; cpu.pc = 0x200; sp = sp.init; run_flag = run_flag.init; draw_flag = draw_flag.init; screen_buf = screen_buf.init; screen_data = screen_data.init; } // reset void step() { cpu.opcode = ram[cpu.pc] << 8 | ram[cpu.pc + 1]; auto pc_target = cast(ProgramCounter)(cpu.pc + 2); writefln("opcode: 0x%X", cpu.opcode); switch (cpu.opcode & 0xF000) with (cpu) { case 0x0000: switch (cpu.opcode & 0x0FFF) { case 0x00E0: // 0x00E0 Clears the screen. screen_buf[0..$] = 0; 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. pc_target = cpu.opcode.capture!(0, 12); break; case 0x2000: // 0x2NNN Calls subroutine at NNN. stack[sp++] = cpu.pc; pc_target = cpu.opcode & 0x0FFF; break; case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN. if (cpu.v[cpu.opcode.capture!(8, 12)] == cpu.opcode.capture!(0, 8)) { pc_target += 2; } break; case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN. if (cpu.v[cpu.opcode.capture!(8, 12)] != cpu.opcode.capture!(0, 8)) { pc_target += 2; } break; case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY. if (cpu.v[cpu.opcode.capture!(8, 12)] == cpu.v[cpu.opcode.capture!(4, 8)]) { pc_target += 2; } break; case 0x6000: // 0x6XNN Sets VX to NN. cpu.v[cpu.opcode.capture!(8, 12)] = cpu.opcode.capture!(0, 8); break; case 0x7000: // 0x7XNN Adds NN to VX. cpu.v[cpu.opcode.capture!(8, 12)] += cpu.opcode.capture!(0, 8); break; case 0x8000: auto x = cpu.opcode.capture!(8, 12); auto y = cpu.opcode.capture!(4, 8); switch (cpu.opcode & 0x000F) { 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. auto vx = cpu.v[x]; auto vy = cpu.v[y]; if (cast(ushort)vx + cast(ushort)vy > 255) { cpu.v[0xF] = 1; } else { cpu.v[0xF] = 0; } cpu.v[x] += cpu.v[y]; 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. auto vx = cpu.v[x]; auto vy = cpu.v[y]; if (vx > vy) { cpu.v[0xF] = 1; } else { cpu.v[0xF] = 0; } cpu.v[x] -= cpu.v[y]; 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. auto vx = cpu.v[x]; cpu.v[0xF] = (vx & 0b10000000) >> 7; 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. auto vx = cpu.v[x]; auto vy = cpu.v[y]; if (vy > vx) { cpu.v[0xF] = 1; } else { cpu.v[0xF] = 0; } cpu.v[x] = cast(Register)(vy - vx); // TODO borrow flag 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. auto vx = cpu.v[x]; cpu.v[0xF] = (vx & 0b10000000) >> 7; cpu.v[x] <<= 1; break; default: // unhandled for some reason writefln("unknown opcode: 0x%x", cpu.opcode); break; } break; case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY. if (cpu.v[cpu.opcode.capture!(8, 12)] != cpu.v[cpu.opcode.capture!(4, 8)]) { pc_target += 2; // do skip yes } break; case 0xA000: // 0xANNN Sets I to the address NNN. cpu.i = cpu.opcode.capture!(0, 12); break; case 0xB000: // 0xBNNN Jumps to the address NNN plus V0. pc_target = cast(ProgramCounter)(cpu.opcode.capture!(0, 12) + cpu.v[0x0]); break; case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN. import std.random : uniform; auto x = cpu.opcode.capture!(0, 8); cpu.v[x] = uniform(Register.min, Register.max) & cpu.opcode.capture!(0, 8); 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: auto spr_addr = cpu.i; auto x = cpu.opcode.capture!(8, 12); auto y = cpu.opcode.capture!(4, 8); auto n = cpu.opcode.capture!(0, 4); foreach(int row; 0 .. n) { ushort pixel = ram[spr_addr + row]; foreach (int col; 0 .. 8) { if ((pixel & 0x80) > 0) { auto x_off = (x + col) % 64; auto y_off = (y + row) % 32; auto offset = x_off + (y_off * 64); if (screen_buf[offset] == 1) { cpu.v[0xF] = 1; } else { cpu.v[0xF] = 0; } screen_buf[offset] ^= 1; writefln("write to offset: %d", offset); } pixel <<= 1; } } draw_flag = true; break; case 0xE000: auto x = cpu.opcode.capture!(8, 12); auto key = cpu.v[x]; 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. cpu.v[cpu.opcode.capture!(8, 12)] = cpu.delay_timer; break; case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX. break; case 0x0015: // 0xFX15 Sets the delay timer to VX. cpu.delay_timer = cpu.v[cpu.opcode.capture!(8, 12)]; break; case 0x0018: // 0xFX18 Sets the sound timer to VX. cpu.sound_timer = cpu.v[cpu.opcode.capture!(8, 12)]; break; case 0x001E: // 0xFX1E Adds VX to I. cpu.i += cpu.v[cpu.opcode.capture!(8, 12)]; break; case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX. auto vx = cpu.v[cpu.opcode.capture!(8, 12)]; ushort char_addr = 0x200 + (vx * 40); // base of char sprites + value of vx * bits per character 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: auto vx = cpu.v[cpu.opcode.capture!(8, 12)]; 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. auto addr = cpu.i; foreach (reg; cpu.v) { ram[addr++] = reg; } break; case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I. auto addr = cpu.i; foreach (ref reg; cpu.v) { reg = ram[addr++]; } break; default: // unhandled for some reason 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) { if (cpu.sound_timer == 1u) { writefln("beep!"); } --cpu.sound_timer; } } // step void handleEvent(ref SDL_Event ev) { } // handleEvent void tick() { if (run_flag) { step(); } } // tick } // Emulator void loadLibs() { DerelictSDL2.load(); DerelictImgui.load(); } void initLibs() { SDL_Init(SDL_INIT_VIDEO); SDL_GL_LoadLibrary(null); } void setupImgui() { } void main() { loadLibs(); initLibs(); Emulator emu; emu.create(); emu.run(); } struct Emulator { bool running; // debug Chip8Status status; Window window; Imgui imgui; Chip8 emu; // drawing PixelBuffer buf; void create() { // create window window.createWindow(960, 768); // setup imgui imgui.initialize(); imgui.createDeviceObjects(); // setup debug ui status.initialize(&this, &emu); // debug data 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; // set up pixel buffer to poke at buf.create(emu.screen_buf.ptr, 64, 32); } void handleEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { imgui.handleEvent(event); status.handleEvent(event); emu.handleEvent(event); switch (event.type) with (SDL_EventType) { case SDL_QUIT: { running = false; break; } default: { break; } } } } // handleEvents void run() { running = true; while (running) { handleEvents(); tick(); draw(); } } void tick() { emu.tick(); } void draw() { window.renderClear(0x428bca); int w, h; window.windowSize(w, h); if (emu.draw_flag) { buf.update(emu.screen_buf); emu.draw_flag = false; } buf.draw(w, h); imgui.newFrame(window); status.draw(); imgui.endFrame(); window.renderPresent(); } void quit() { running = false; } }