aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaghuram Subramani <raghus2247@gmail.com>2023-10-21 15:59:54 +0530
committerRaghuram Subramani <raghus2247@gmail.com>2023-10-21 21:57:47 +0530
commit6f5020bb6c198e475982e207179605b314b1afbe (patch)
treed1350c1f423c56ed034ec5e66ba46ac8c4b20833
parent6b9606398e8f466dc3f0aa60596a3a574f7af57e (diff)
Initial code
-rw-r--r--.gitignore3
-rw-r--r--CMakeLists.txt12
-rw-r--r--src/chip8.cpp520
-rw-r--r--src/chip8.hpp69
-rw-r--r--src/main.cpp40
-rw-r--r--src/platform.cpp80
-rw-r--r--src/platform.hpp24
7 files changed, 748 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f54ec33
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+compile_commands.json
+build/
+.ccls-cache/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..101d22f
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 3.12)
+project(chip8emu)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+find_package(SDL2 REQUIRED)
+
+include_directories(src)
+file(GLOB SOURCES src/*.cpp)
+add_executable(chip8emu ${SOURCES})
+
+# Link SDL2 library
+target_link_libraries(chip8emu PRIVATE SDL2::SDL2)
diff --git a/src/chip8.cpp b/src/chip8.cpp
new file mode 100644
index 0000000..94573f0
--- /dev/null
+++ b/src/chip8.cpp
@@ -0,0 +1,520 @@
+#include "chip8.hpp"
+
+#include <fstream>
+#include <iostream>
+#include <cstdint>
+#include <chrono>
+
+const uint32_t FONTSET_START_ADDRESS = 0x050;
+const uint32_t ROM_START_ADDRESS = 0x200;
+
+const uint32_t FONTSET_SIZE = 0x0a0 - FONTSET_START_ADDRESS;
+
+uint8_t fontset[FONTSET_SIZE] = {
+ 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
+};
+
+Chip8::Chip8() {
+ pc = ROM_START_ADDRESS;
+
+ for (uint32_t i = 0; i < FONTSET_SIZE; i++) {
+ memory[FONTSET_START_ADDRESS + i] = fontset[i];
+ }
+
+ randGen = std::default_random_engine(
+ std::chrono::system_clock::now()
+ .time_since_epoch()
+ .count()
+ );
+ randByte = std::uniform_int_distribution<uint8_t>(0, 255U);
+
+ SetupTable();
+}
+
+void Chip8::SetupTable() {
+ for (size_t i = 0; i <= sizeof(table0) / sizeof(table0[0]); i++) {
+ table0[i] = &Chip8::OP_NULL;
+ table8[i] = &Chip8::OP_NULL;
+ tableE[i] = &Chip8::OP_NULL;
+ }
+
+ for (size_t i = 0; i <= sizeof(tableF) / sizeof(tableF[0]); i++) {
+ tableF[i] = &Chip8::OP_NULL;
+ }
+
+ table[0x0] = &Chip8::Table0;
+ table[0x1] = &Chip8::OP_1nnn;
+ table[0x2] = &Chip8::OP_2nnn;
+ table[0x3] = &Chip8::OP_3xkk;
+ table[0x4] = &Chip8::OP_4xkk;
+ table[0x5] = &Chip8::OP_5xy0;
+ table[0x6] = &Chip8::OP_6xkk;
+ table[0x7] = &Chip8::OP_7xkk;
+ table[0x8] = &Chip8::Table8;
+ table[0x9] = &Chip8::OP_9xy0;
+ table[0xA] = &Chip8::OP_Annn;
+ table[0xB] = &Chip8::OP_Bnnn;
+ table[0xC] = &Chip8::OP_Cxkk;
+ table[0xD] = &Chip8::OP_Dxyn;
+ table[0xE] = &Chip8::TableE;
+ table[0xF] = &Chip8::TableF;
+
+ table0[0x0] = &Chip8::OP_00E0;
+ table0[0xE] = &Chip8::OP_00EE;
+
+ table8[0x0] = &Chip8::OP_8xy0;
+ table8[0x1] = &Chip8::OP_8xy1;
+ table8[0x2] = &Chip8::OP_8xy2;
+ table8[0x3] = &Chip8::OP_8xy3;
+ table8[0x4] = &Chip8::OP_8xy4;
+ table8[0x5] = &Chip8::OP_8xy5;
+ table8[0x6] = &Chip8::OP_8xy6;
+ table8[0x7] = &Chip8::OP_8xy7;
+ table8[0xE] = &Chip8::OP_8xyE;
+
+ tableE[0x1] = &Chip8::OP_ExA1;
+ tableE[0xE] = &Chip8::OP_Ex9E;
+
+ tableF[0x07] = &Chip8::OP_Fx07;
+ tableF[0x0A] = &Chip8::OP_Fx0A;
+ tableF[0x15] = &Chip8::OP_Fx15;
+ tableF[0x18] = &Chip8::OP_Fx18;
+ tableF[0x1E] = &Chip8::OP_Fx1E;
+ tableF[0x29] = &Chip8::OP_Fx29;
+ tableF[0x33] = &Chip8::OP_Fx33;
+ tableF[0x55] = &Chip8::OP_Fx55;
+ tableF[0x65] = &Chip8::OP_Fx65;
+}
+
+void Chip8::Table0() {
+ ((*this).*(table0[opcode & 0x000Fu]))();
+}
+
+void Chip8::Table8() {
+ ((*this).*(table8[opcode & 0x000Fu]))();
+}
+
+void Chip8::TableE() {
+ ((*this).*(tableE[opcode & 0x000Fu]))();
+}
+
+void Chip8::TableF() {
+ ((*this).*(tableF[opcode & 0x00FFu]))();
+}
+
+void Chip8::Cycle() {
+ // Fetch
+ opcode = (memory[pc] << 8u) | memory[pc + 1];
+ pc += 2;
+
+ // Decode and Execute
+ ((*this).*(table[(opcode & 0xF000u) >> 12u]))();
+
+ // Decrement delay timer
+ if (delayTimer > 0) {
+ delayTimer--;
+ }
+
+ if (soundTimer > 0) {
+ --soundTimer;
+ }
+}
+
+void Chip8::LoadROM(const char* filename) {
+ // Open file as binary stream and seek to the end
+ std::ifstream file(filename, std::ios::binary | std::ios::ate);
+
+ if (file.is_open()) {
+ // Get file size
+ std::streampos size = file.tellg();
+ // Allocate buffer
+ char* buffer = new char[size];
+
+ // Seek to the beginning
+ file.seekg(0, std::ios::beg);
+ // Populate the buffer
+ file.read(buffer, size);
+ file.close();
+
+ // Load ROM contents into Chip8's memory, from 0x200
+ for (uint64_t i = 0; i < size; i++) {
+ memory[ROM_START_ADDRESS + i] = buffer[i];
+ }
+
+ // Free the buffer
+ delete[] buffer;
+ }
+}
+
+// NULL: NOP
+// Do nothing
+void Chip8::OP_NULL() {}
+
+// 00E0: CLS
+// Clear the display
+void Chip8::OP_00E0() {
+ for (uint32_t i = 0; i < sizeof(video) / sizeof(video[0]); i++) {
+ video[i] = 0;
+ }
+};
+
+// 00EE: RET
+// Return from a subroutine
+void Chip8::OP_00EE() {
+ sp--;
+ pc = stack[sp];
+}
+
+// 1nnn: JP addr
+// Jump to location nnn
+void Chip8::OP_1nnn() {
+ uint16_t address = opcode & 0x0FFFu;
+ pc = address;
+}
+
+// 2nnn: CALL addr
+// Call subroutine at nnn
+void Chip8::OP_2nnn() {
+ uint16_t address = opcode & 0x0FFFu;
+ stack[sp] = pc;
+ sp++;
+ pc = address;
+}
+
+// 3xkk: SE Vx, byte
+// Skip next instruction if Vx = kk
+void Chip8::OP_3xkk() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t byte = opcode & 0x00FFu;
+
+ if (registers[Vx] == byte)
+ pc += 2;
+}
+
+// 4xkk: SNE Vx, byte
+// Skip next instruction if Vx != kk
+void Chip8::OP_4xkk() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t byte = opcode & 0x00FFu;
+
+ if (registers[Vx] != byte)
+ pc += 2;
+}
+
+// 5xy0: SE Vx, Vy
+// Skip next instruction if Vx == Vy
+void Chip8::OP_5xy0() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ if (registers[Vx] == registers[Vy])
+ pc += 2;
+}
+
+// 6xkk: LD Vx, byte
+// Set Vx = kk
+void Chip8::OP_6xkk() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t byte = opcode & 0x00FFu;
+
+ registers[Vx] = byte;
+}
+
+// 7xkk: ADD Vx, byte
+// Set Vx += kk
+void Chip8::OP_7xkk() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t byte = opcode & 0x00FFu;
+
+ registers[Vx] += byte;
+}
+
+// 8xy0: LD Vx, Vy
+// Set Vx = Vy
+void Chip8::OP_8xy0() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ registers[Vx] = registers[Vy];
+}
+
+// 8xy1: OR Vx, Vy
+// Set Vx = Vx OR Vy
+void Chip8::OP_8xy1() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ registers[Vx] |= registers[Vy];
+}
+
+// 8xy2: AND Vx, Vy
+// Set Vx = Vx AND Vy
+void Chip8::OP_8xy2() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ registers[Vx] &= registers[Vy];
+}
+
+// 8xy3: XOR Vx, Vy
+// Set Vx = Vx XOR Vy
+void Chip8::OP_8xy3() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ registers[Vx] ^= registers[Vy];
+}
+
+// 8xy4: ADD Vx, Vy
+// Set Vx += Vy, set VF = carry
+void Chip8::OP_8xy4() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ uint16_t sum = registers[Vx] + registers[Vy];
+
+ if (sum > 255u) {
+ registers[0xF] = 1;
+ } else {
+ registers[0xF] = 0;
+ }
+
+ registers[Vx] = sum & 0xFFu;
+}
+
+// 8xy5: SUB Vx, Vy
+// Set Vx -= Vy, set VF = NOT borrow
+void Chip8::OP_8xy5() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ if (registers[Vx] > registers[Vy]) {
+ registers[0xF] = 1;
+ } else {
+ registers[0xF] = 0;
+ }
+
+ registers[Vx] -= registers[Vy];
+}
+
+// 8xy6: SHR Vx
+// Set Vx = Vx SHR 1
+void Chip8::OP_8xy6() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+
+ // Save LSB in VF
+ registers[0xF] = (registers[Vx] & 0x1u);
+
+ registers[Vx] >>= 1;
+}
+
+// 8xy7: SUBN Vx, Vy
+// Set Vy -= Vx, set VF = NOT borrow
+void Chip8::OP_8xy7() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ if (registers[Vy] > registers[Vx]) {
+ registers[0xF] = 1;
+ } else {
+ registers[0xF] = 0;
+ }
+
+ registers[Vx] = registers[Vy] - registers[Vx];
+}
+
+// 8xyE: SHL Vx
+// Set Vx = Vx SHL 1
+void Chip8::OP_8xyE() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+
+ // Save LSB in VF
+ registers[0xF] = (registers[Vx] & 0x80u) >> 7u;
+
+ registers[Vx] <<= 1;
+}
+
+// 9xy0: SNE Vx, Vy
+// Skip next instruction if Vx != Vy
+void Chip8::OP_9xy0() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+
+ if (registers[Vx] != registers[Vy])
+ pc += 2;
+}
+
+// Annn: LD I, addr
+// Set I = nnn
+void Chip8::OP_Annn() {
+ uint16_t address = opcode & 0x0FFFu;
+ index = address;
+}
+
+// Bnnn: JP V0, addr
+// Jump to location nnn + V0
+void Chip8::OP_Bnnn() {
+ uint16_t address = opcode & 0x0FFFu;
+ pc = registers[0x0] + address;
+}
+
+// Cxkk: RND Vx, byte
+// Set Vx = random byte AND kk
+void Chip8::OP_Cxkk() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t byte = opcode & 0x00FFu;
+
+ registers[Vx] = randByte(randGen) & byte;
+}
+
+// Dxyn: DRW Vx, Vy, nibble
+// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision
+void Chip8::OP_Dxyn() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t Vy = (opcode & 0x00FFu) >> 4u;
+ uint8_t height = opcode & 0x000Fu;
+
+ // Wrap if going beyond screen boundaries
+ uint8_t xPos = registers[Vx] % VIDEO_WIDTH;
+ uint8_t yPos = registers[Vy] % VIDEO_HEIGHT;
+
+ registers[0xF] = 0;
+
+ for (uint32_t row = 0; row < height; ++row) {
+ uint8_t spriteByte = memory[index + row];
+ for (uint32_t col = 0; col < 8; ++col) {
+ uint8_t spritePixel = spriteByte & (0x80u >> col);
+ uint32_t* screenPixel = &video[(yPos + row) * VIDEO_WIDTH + (xPos + col)];
+
+ if (spritePixel) {
+ if(*screenPixel == 0xFFFFFFFF)
+ registers[0xF] = 1;
+ *screenPixel ^= 0xFFFFFFFF;
+ }
+ }
+ }
+}
+
+// Ex9E: SKP Vx
+// Skip next instruction if key with the value of Vx is pressed
+void Chip8::OP_Ex9E() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t key = registers[Vx];
+
+ if (keypad[key])
+ pc += 2;
+}
+
+// ExA1: SKNP Vx
+// Skip next instruction if key with the value of Vx is not pressed
+void Chip8::OP_ExA1() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t key = registers[Vx];
+
+ if (!keypad[key])
+ pc += 2;
+}
+
+// Fx07: LD Vx, DT
+// Set Vx = delay timer value
+void Chip8::OP_Fx07() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ registers[Vx] = delayTimer;
+}
+
+// Fx0A: LD Vx, K
+// Wait for a key press, store the value of the key in Vx
+void Chip8::OP_Fx0A() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ for (uint16_t i = 0; i < 16; i++) {
+ if (keypad[i]) {
+ registers[Vx] = static_cast<uint8_t>(i);
+ return;
+ }
+ }
+
+ pc -= 2;
+}
+
+// Fx15: LD DT, Vx
+// Set delay timer value = Vx
+void Chip8::OP_Fx15() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ delayTimer = registers[Vx];
+}
+
+// Fx18: LD ST, Vx
+// Set sound timer value = Vx
+void Chip8::OP_Fx18() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ soundTimer = registers[Vx];
+}
+
+// Fx1E: ADD I, Vx
+// Set I += Vx
+void Chip8::OP_Fx1E() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ index += registers[Vx];
+}
+
+// Fx29: LD F, Vx
+// Set I = location of sprite for digit Vx
+void Chip8::OP_Fx29() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t digit = registers[Vx];
+
+ index = FONTSET_START_ADDRESS + (5 * digit);
+}
+
+// Fx33: LD B, Vx
+// Store BCD representation of Vx in memory locations I, I+1, and I+2
+void Chip8::OP_Fx33() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+ uint8_t value = registers[Vx];
+
+ // Ones-place
+ memory[index + 2] = value % 10;
+ value /= 10;
+
+ // Tens-place
+ memory[index + 1] = value % 10;
+ value /= 10;
+
+ // Hundreds-place
+ memory[index] = value % 10;
+}
+
+// Fx55: LD [I], Vx
+// Store registers V0 through Vx in memory starting at location I
+void Chip8::OP_Fx55() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+
+ for (uint8_t i = 0; i <= Vx; ++i) {
+ memory[index + i] = registers[i];
+ }
+}
+
+// Fx65: LD Vx, [I]
+// Read registers V0 through Vx in memory starting at location I
+void Chip8::OP_Fx65() {
+ uint8_t Vx = (opcode & 0x0F00u) >> 8u;
+
+ for (uint8_t i = 0; i <= Vx; ++i) {
+ registers[i] = memory[index + i];
+ }
+}
diff --git a/src/chip8.hpp b/src/chip8.hpp
new file mode 100644
index 0000000..f144341
--- /dev/null
+++ b/src/chip8.hpp
@@ -0,0 +1,69 @@
+#ifndef CHIP8_H_
+#define CHIP8_H_
+
+#include <cstdint>
+#include <random>
+
+const uint32_t VIDEO_HEIGHT = 32;
+const uint32_t VIDEO_WIDTH = 64;
+
+class Chip8 {
+ public:
+ Chip8();
+
+ public:
+ uint8_t registers[16] {};
+ uint8_t memory[4096] {};
+
+ uint16_t index {};
+ uint16_t pc {};
+ uint16_t stack[16] {};
+ uint8_t sp {};
+
+ uint8_t delayTimer {};
+ uint8_t soundTimer {};
+
+ uint8_t keypad[16] {};
+ uint32_t video[VIDEO_WIDTH * VIDEO_HEIGHT] {};
+
+ uint16_t opcode;
+
+ private:
+ std::default_random_engine randGen;
+ std::uniform_int_distribution<uint8_t> randByte;
+
+ public:
+ void LoadROM(const char* filename);
+ void SetupTable();
+
+ public:
+ void Cycle();
+
+ public:
+ typedef void (Chip8::*OpcodeFunction)();
+ OpcodeFunction table[0x10u];
+ OpcodeFunction table0[0xFu];
+ OpcodeFunction table8[0xFu];
+ OpcodeFunction tableE[0xFu];
+ OpcodeFunction tableF[0x65u];
+
+ void Table0();
+ void Table8();
+ void TableE();
+ void TableF();
+
+ void OP_NULL(); void OP_00E0(); void OP_00EE();
+ void OP_1nnn(); void OP_2nnn(); void OP_3xkk();
+ void OP_4xkk(); void OP_5xy0(); void OP_6xkk();
+ void OP_7xkk(); void OP_8xy0(); void OP_8xy1();
+ void OP_8xy2(); void OP_8xy3(); void OP_8xy4();
+ void OP_8xy5(); void OP_8xy6(); void OP_8xy7();
+ void OP_8xyE(); void OP_9xy0(); void OP_Annn();
+ void OP_Bnnn(); void OP_Cxkk(); void OP_Dxyn();
+ void OP_Ex9E(); void OP_ExA1(); void OP_Fx07();
+ void OP_Fx0A(); void OP_Fx15(); void OP_Fx18();
+ void OP_Fx1E(); void OP_Fx29(); void OP_Fx33();
+ void OP_Fx55(); void OP_Fx65();
+};
+
+#endif
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..b5f12df
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,40 @@
+#include <iostream>
+#include <unistd.h>
+
+#include "chip8.hpp"
+#include "platform.hpp"
+
+int main(int argc, char** argv) {
+ if (argc != 4) {
+ std::cerr << "Usage: " << argv[0] << " <Scale> <Delay> <ROM>\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ int videoScale = std::stoi(argv[1]);
+ int cycleDelay = std::stoi(argv[2]);
+ char const* filename = argv[3];
+
+ Platform platform(
+ "chip8emu",
+ VIDEO_WIDTH * videoScale,
+ VIDEO_HEIGHT * videoScale,
+ VIDEO_WIDTH, VIDEO_HEIGHT
+ );
+
+ Chip8 chip8;
+ chip8.LoadROM(filename);
+
+ int videoPitch = sizeof(chip8.video[0]) * VIDEO_WIDTH;
+
+ bool quit = false;
+ while (!quit) {
+ quit = platform.ProcessInput(chip8.keypad);
+
+ usleep(cycleDelay * 1000);
+
+ chip8.Cycle();
+ platform.Update(chip8.video, videoPitch);
+ }
+
+ return 0;
+}
diff --git a/src/platform.cpp b/src/platform.cpp
new file mode 100644
index 0000000..c7d9ab3
--- /dev/null
+++ b/src/platform.cpp
@@ -0,0 +1,80 @@
+#include "platform.hpp"
+
+Platform::Platform(const char* title, int windowWidth, int windowHeight, int textureWidth, int textureHeight) {
+ SDL_Init(SDL_INIT_VIDEO);
+
+ window = SDL_CreateWindow(title, 0, 0, windowWidth, windowHeight, SDL_WINDOW_SHOWN);
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+ texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, textureWidth, textureHeight);
+}
+
+Platform::~Platform() {
+ SDL_DestroyTexture(texture);
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+}
+
+void Platform::Update(const void* buffer, int pitch) {
+ SDL_UpdateTexture(texture, nullptr, buffer, pitch);
+ SDL_RenderClear(renderer);
+ SDL_RenderCopy(renderer, texture, nullptr, nullptr);
+ SDL_RenderPresent(renderer);
+}
+
+bool Platform::ProcessInput(uint8_t* keys) {
+ bool quit = false;
+
+ SDL_Event event;
+
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ quit = true;
+ break;
+
+ case SDL_KEYDOWN:
+ switch (event.key.keysym.sym) {
+ case SDLK_ESCAPE: quit = true;
+ case SDLK_x: keys[0x0] = 1; break;
+ case SDLK_1: keys[0x1] = 1; break;
+ case SDLK_2: keys[0x2] = 1; break;
+ case SDLK_3: keys[0x3] = 1; break;
+ case SDLK_q: keys[0x4] = 1; break;
+ case SDLK_w: keys[0x5] = 1; break;
+ case SDLK_e: keys[0x6] = 1; break;
+ case SDLK_a: keys[0x7] = 1; break;
+ case SDLK_s: keys[0x8] = 1; break;
+ case SDLK_d: keys[0x9] = 1; break;
+ case SDLK_z: keys[0xA] = 1; break;
+ case SDLK_c: keys[0xB] = 1; break;
+ case SDLK_4: keys[0xC] = 1; break;
+ case SDLK_r: keys[0xD] = 1; break;
+ case SDLK_f: keys[0xE] = 1; break;
+ case SDLK_v: keys[0xF] = 1; break;
+ } break;
+
+ case SDL_KEYUP:
+ switch (event.key.keysym.sym) {
+ case SDLK_x: keys[0x0] = 0; break;
+ case SDLK_1: keys[0x1] = 0; break;
+ case SDLK_2: keys[0x2] = 0; break;
+ case SDLK_3: keys[0x3] = 0; break;
+ case SDLK_q: keys[0x4] = 0; break;
+ case SDLK_w: keys[0x5] = 0; break;
+ case SDLK_e: keys[0x6] = 0; break;
+ case SDLK_a: keys[0x7] = 0; break;
+ case SDLK_s: keys[0x8] = 0; break;
+ case SDLK_d: keys[0x9] = 0; break;
+ case SDLK_z: keys[0xA] = 0; break;
+ case SDLK_c: keys[0xB] = 0; break;
+ case SDLK_4: keys[0xC] = 0; break;
+ case SDLK_r: keys[0xD] = 0; break;
+ case SDLK_f: keys[0xE] = 0; break;
+ case SDLK_v: keys[0xF] = 0; break;
+ }
+ }
+ }
+
+ return quit;
+}
diff --git a/src/platform.hpp b/src/platform.hpp
new file mode 100644
index 0000000..55b91df
--- /dev/null
+++ b/src/platform.hpp
@@ -0,0 +1,24 @@
+#ifndef PLATFORM_HPP_
+#define PLATFORM_HPP_
+
+#include <cstdint>
+#include <SDL.h>
+
+class Platform {
+ public:
+ Platform(
+ const char* title, int windowWidth, int windowHeight, int textureWidth, int textureHeight
+ );
+ ~Platform();
+
+ public:
+ void Update(const void* buffer, int pitch);
+ bool ProcessInput(uint8_t* keys);
+
+ private:
+ SDL_Window* window {};
+ SDL_Renderer* renderer {};
+ SDL_Texture* texture {};
+};
+
+#endif