diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7815fb2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.14) +project(chip8) + +set(CMAKE_CXX_STANDARD 11) + +find_package(SDL2 REQUIRED) + +add_executable( + chip8 + Source/Chip8.cpp + Source/Main.cpp + Source/Platform.cpp) + +target_compile_options(chip8 PRIVATE -Wall) + +target_link_libraries(chip8 PRIVATE SDL2::SDL2) diff --git a/Source/Chip8.cpp b/Source/Chip8.cpp new file mode 100644 index 0000000..a4a9df7 --- /dev/null +++ b/Source/Chip8.cpp @@ -0,0 +1,583 @@ +#include "Chip8.hpp" +#include +#include +#include +#include +#include + + +const unsigned int FONTSET_SIZE = 80; +const unsigned int FONTSET_START_ADDRESS = 0x50; +const unsigned int START_ADDRESS = 0x200; + + +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() + : randGen(std::chrono::system_clock::now().time_since_epoch().count()) +{ + // Initialize PC + pc = START_ADDRESS; + + // Load fonts into memory + for (unsigned int i = 0; i < FONTSET_SIZE; ++i) + { + memory[FONTSET_START_ADDRESS + i] = fontset[i]; + } + + // Initialize RNG + randByte = std::uniform_int_distribution(0, 255U); + + // Set up function pointer table + 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::LoadROM(char const* filename) +{ + std::ifstream file(filename, std::ios::binary | std::ios::ate); + + if (file.is_open()) + { + std::streampos size = file.tellg(); + char* buffer = new char[size]; + file.seekg(0, std::ios::beg); + file.read(buffer, size); + file.close(); + + for (long i = 0; i < size; ++i) + { + memory[START_ADDRESS + i] = buffer[i]; + } + + delete[] buffer; + } +} + +void Chip8::Cycle() +{ + // Fetch + opcode = (memory[pc] << 8u) | memory[pc + 1]; + + // Increment the PC before we execute anything + pc += 2; + + // Decode and Execute + ((*this).*(table[(opcode & 0xF000u) >> 12u]))(); + + // Decrement the delay timer if it's been set + if (delayTimer > 0) + { + --delayTimer; + } + + // Decrement the sound timer if it's been set + if (soundTimer > 0) + { + --soundTimer; + } +} + +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::OP_NULL() +{} + +void Chip8::OP_00E0() +{ + memset(video, 0, VIDEO_HEIGHT * VIDEO_WIDTH); +} + +void Chip8::OP_00EE() +{ + --sp; + pc = stack[sp]; +} + +void Chip8::OP_1nnn() +{ + uint16_t address = opcode & 0x0FFFu; + + pc = address; +} + +void Chip8::OP_2nnn() +{ + uint16_t address = opcode & 0x0FFFu; + + stack[sp] = pc; + ++sp; + pc = address; +} + +void Chip8::OP_3xkk() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t byte = opcode & 0x00FFu; + + if (registers[Vx] == byte) + { + pc += 2; + } +} + +void Chip8::OP_4xkk() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t byte = opcode & 0x00FFu; + + if (registers[Vx] != byte) + { + pc += 2; + } +} + +void Chip8::OP_5xy0() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + if (registers[Vx] == registers[Vy]) + { + pc += 2; + } +} + +void Chip8::OP_6xkk() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t byte = opcode & 0x00FFu; + + registers[Vx] = byte; +} + +void Chip8::OP_7xkk() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t byte = opcode & 0x00FFu; + + registers[Vx] += byte; +} + +void Chip8::OP_8xy0() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + registers[Vx] = registers[Vy]; +} + +void Chip8::OP_8xy1() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + registers[Vx] |= registers[Vy]; +} + +void Chip8::OP_8xy2() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + registers[Vx] &= registers[Vy]; +} + +void Chip8::OP_8xy3() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + registers[Vx] ^= registers[Vy]; +} + +void Chip8::OP_8xy4() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + uint16_t sum = registers[Vx] + registers[Vy]; + + if (sum > 255U) + { + registers[0xF] = 1; + } + else + { + registers[0xF] = 0; + } + + registers[Vx] = sum & 0xFFu; +} + +void Chip8::OP_8xy5() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + if (registers[Vx] > registers[Vy]) + { + registers[0xF] = 1; + } + else + { + registers[0xF] = 0; + } + + registers[Vx] -= registers[Vy]; +} + +void Chip8::OP_8xy6() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + // Save LSB in VF + registers[0xF] = (registers[Vx] & 0x1u); + + registers[Vx] >>= 1; +} + +void Chip8::OP_8xy7() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + if (registers[Vy] > registers[Vx]) + { + registers[0xF] = 1; + } + else + { + registers[0xF] = 0; + } + + registers[Vx] = registers[Vy] - registers[Vx]; +} + +void Chip8::OP_8xyE() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + // Save MSB in VF + registers[0xF] = (registers[Vx] & 0x80u) >> 7u; + + registers[Vx] <<= 1; +} + +void Chip8::OP_9xy0() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 4u; + + if (registers[Vx] != registers[Vy]) + { + pc += 2; + } +} + +void Chip8::OP_Annn() +{ + uint16_t address = opcode & 0x0FFFu; + + index = address; +} + +void Chip8::OP_Bnnn() +{ + uint16_t address = opcode & 0x0FFFu; + + pc = registers[0] + address; +} + +void Chip8::OP_Cxkk() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t byte = opcode & 0x00FFu; + + registers[Vx] = randByte(randGen) & byte; +} + +void Chip8::OP_Dxyn() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t Vy = (opcode & 0x00F0u) >> 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 (unsigned int row = 0; row < height; ++row) + { + uint8_t spriteByte = memory[index + row]; + + for (unsigned int col = 0; col < 8; ++col) + { + uint8_t spritePixel = spriteByte & (0x80u >> col); + uint32_t* screenPixel = &video[(yPos + row) * VIDEO_WIDTH + (xPos + col)]; + + // Sprite pixel is on + if (spritePixel) + { + // Screen pixel also on - collision + if (*screenPixel == 0xFFFFFFFF) + { + registers[0xF] = 1; + } + + // Effectively XOR with the sprite pixel + *screenPixel ^= 0xFFFFFFFF; + } + } + } +} + +void Chip8::OP_Ex9E() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + uint8_t key = registers[Vx]; + + if (keypad[key]) + { + pc += 2; + } +} + +void Chip8::OP_ExA1() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + uint8_t key = registers[Vx]; + + if (!keypad[key]) + { + pc += 2; + } +} + +void Chip8::OP_Fx07() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + registers[Vx] = delayTimer; +} + +void Chip8::OP_Fx0A() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + if (keypad[0]) + { + registers[Vx] = 0; + } + else if (keypad[1]) + { + registers[Vx] = 1; + } + else if (keypad[2]) + { + registers[Vx] = 2; + } + else if (keypad[3]) + { + registers[Vx] = 3; + } + else if (keypad[4]) + { + registers[Vx] = 4; + } + else if (keypad[5]) + { + registers[Vx] = 5; + } + else if (keypad[6]) + { + registers[Vx] = 6; + } + else if (keypad[7]) + { + registers[Vx] = 7; + } + else if (keypad[8]) + { + registers[Vx] = 8; + } + else if (keypad[9]) + { + registers[Vx] = 9; + } + else if (keypad[10]) + { + registers[Vx] = 10; + } + else if (keypad[11]) + { + registers[Vx] = 11; + } + else if (keypad[12]) + { + registers[Vx] = 12; + } + else if (keypad[13]) + { + registers[Vx] = 13; + } + else if (keypad[14]) + { + registers[Vx] = 14; + } + else if (keypad[15]) + { + registers[Vx] = 15; + } + else + { + pc -= 2; + } +} + +void Chip8::OP_Fx15() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + delayTimer = registers[Vx]; +} + +void Chip8::OP_Fx18() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + soundTimer = registers[Vx]; +} + +void Chip8::OP_Fx1E() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + index += registers[Vx]; +} + +void Chip8::OP_Fx29() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + uint8_t digit = registers[Vx]; + + index = FONTSET_START_ADDRESS + (5 * digit); +} + +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; +} + +void Chip8::OP_Fx55() +{ + uint8_t Vx = (opcode & 0x0F00u) >> 8u; + + for (uint8_t i = 0; i <= Vx; ++i) + { + memory[index + i] = registers[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/Source/Chip8.hpp b/Source/Chip8.hpp new file mode 100644 index 0000000..cba6cef --- /dev/null +++ b/Source/Chip8.hpp @@ -0,0 +1,155 @@ +#pragma once + +#include +#include + + +const unsigned int KEY_COUNT = 16; +const unsigned int MEMORY_SIZE = 4096; +const unsigned int REGISTER_COUNT = 16; +const unsigned int STACK_LEVELS = 16; +const unsigned int VIDEO_HEIGHT = 32; +const unsigned int VIDEO_WIDTH = 64; + + +class Chip8 +{ +public: + Chip8(); + void LoadROM(char const* filename); + void Cycle(); + + uint8_t keypad[KEY_COUNT]{}; + uint32_t video[VIDEO_WIDTH * VIDEO_HEIGHT]{}; + +private: + void Table0(); + void Table8(); + void TableE(); + void TableF(); + + // Do nothing + void OP_NULL(); + + // CLS + void OP_00E0(); + + // RET + void OP_00EE(); + + // JP address + void OP_1nnn(); + + // CALL address + void OP_2nnn(); + + // SE Vx, byte + void OP_3xkk(); + + // SNE Vx, byte + void OP_4xkk(); + + // SE Vx, Vy + void OP_5xy0(); + + // LD Vx, byte + void OP_6xkk(); + + // ADD Vx, byte + void OP_7xkk(); + + // LD Vx, Vy + void OP_8xy0(); + + // OR Vx, Vy + void OP_8xy1(); + + // AND Vx, Vy + void OP_8xy2(); + + // XOR Vx, Vy + void OP_8xy3(); + + // ADD Vx, Vy + void OP_8xy4(); + + // SUB Vx, Vy + void OP_8xy5(); + + // SHR Vx + void OP_8xy6(); + + // SUBN Vx, Vy + void OP_8xy7(); + + // SHL Vx + void OP_8xyE(); + + // SNE Vx, Vy + void OP_9xy0(); + + // LD I, address + void OP_Annn(); + + // JP V0, address + void OP_Bnnn(); + + // RND Vx, byte + void OP_Cxkk(); + + // DRW Vx, Vy, height + void OP_Dxyn(); + + // SKP Vx + void OP_Ex9E(); + + // SKNP Vx + void OP_ExA1(); + + // LD Vx, DT + void OP_Fx07(); + + // LD Vx, K + void OP_Fx0A(); + + // LD DT, Vx + void OP_Fx15(); + + // LD ST, Vx + void OP_Fx18(); + + // ADD I, Vx + void OP_Fx1E(); + + // LD F, Vx + void OP_Fx29(); + + // LD B, Vx + void OP_Fx33(); + + // LD [I], Vx + void OP_Fx55(); + + // LD Vx, [I] + void OP_Fx65(); + + uint8_t memory[MEMORY_SIZE]{}; + uint8_t registers[REGISTER_COUNT]{}; + uint16_t index{}; + uint16_t pc{}; + uint8_t delayTimer{}; + uint8_t soundTimer{}; + uint16_t stack[STACK_LEVELS]{}; + uint8_t sp{}; + uint16_t opcode{}; + + std::default_random_engine randGen; + std::uniform_int_distribution randByte; + + typedef void (Chip8::*Chip8Func)(); + Chip8Func table[0xF + 1]{&Chip8::OP_NULL}; + Chip8Func table0[0xE + 1]{&Chip8::OP_NULL}; + Chip8Func table8[0xE + 1]{&Chip8::OP_NULL}; + Chip8Func tableE[0xE + 1]{&Chip8::OP_NULL}; + Chip8Func tableF[0x65 + 1]{&Chip8::OP_NULL}; +}; diff --git a/Source/Main.cpp b/Source/Main.cpp new file mode 100644 index 0000000..9702eb7 --- /dev/null +++ b/Source/Main.cpp @@ -0,0 +1,47 @@ +#include "Chip8.hpp" +#include "Platform.hpp" +#include +#include + + +int main(int argc, char** argv) +{ + if (argc != 4) + { + std::cerr << "Usage: " << argv[0] << " \n"; + std::exit(EXIT_FAILURE); + } + + int videoScale = std::stoi(argv[1]); + int cycleDelay = std::stoi(argv[2]); + char const* romFilename = argv[3]; + + Platform platform("CHIP-8 Emulator", VIDEO_WIDTH * videoScale, VIDEO_HEIGHT * videoScale, VIDEO_WIDTH, VIDEO_HEIGHT); + + Chip8 chip8; + chip8.LoadROM(romFilename); + + int videoPitch = sizeof(chip8.video[0]) * VIDEO_WIDTH; + + auto lastCycleTime = std::chrono::high_resolution_clock::now(); + bool quit = false; + + while (!quit) + { + quit = platform.ProcessInput(chip8.keypad); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float dt = std::chrono::duration(currentTime - lastCycleTime).count(); + + if (dt > cycleDelay) + { + lastCycleTime = currentTime; + + chip8.Cycle(); + + platform.Update(chip8.video, videoPitch); + } + } + + return 0; +} diff --git a/Source/Platform.cpp b/Source/Platform.cpp new file mode 100644 index 0000000..b4199d5 --- /dev/null +++ b/Source/Platform.cpp @@ -0,0 +1,229 @@ +#include "Platform.hpp" +#include + + +Platform::Platform(char const* 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(void const* 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; + } break; + + case SDLK_x: + { + keys[0] = 1; + } break; + + case SDLK_1: + { + keys[1] = 1; + } break; + + case SDLK_2: + { + keys[2] = 1; + } break; + + case SDLK_3: + { + keys[3] = 1; + } break; + + case SDLK_q: + { + keys[4] = 1; + } break; + + case SDLK_w: + { + keys[5] = 1; + } break; + + case SDLK_e: + { + keys[6] = 1; + } break; + + case SDLK_a: + { + keys[7] = 1; + } break; + + case SDLK_s: + { + keys[8] = 1; + } break; + + case SDLK_d: + { + keys[9] = 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[0] = 0; + } break; + + case SDLK_1: + { + keys[1] = 0; + } break; + + case SDLK_2: + { + keys[2] = 0; + } break; + + case SDLK_3: + { + keys[3] = 0; + } break; + + case SDLK_q: + { + keys[4] = 0; + } break; + + case SDLK_w: + { + keys[5] = 0; + } break; + + case SDLK_e: + { + keys[6] = 0; + } break; + + case SDLK_a: + { + keys[7] = 0; + } break; + + case SDLK_s: + { + keys[8] = 0; + } break; + + case SDLK_d: + { + keys[9] = 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; + } + } break; + } + } + + return quit; +} + diff --git a/Source/Platform.hpp b/Source/Platform.hpp new file mode 100644 index 0000000..bcf7e30 --- /dev/null +++ b/Source/Platform.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + + +class SDL_Window; +class SDL_Renderer; +class SDL_Texture; + + +class Platform +{ +public: + Platform(char const* title, int windowWidth, int windowHeight, int textureWidth, int textureHeight); + ~Platform(); + void Update(void const* buffer, int pitch); + bool ProcessInput(uint8_t* keys); + +private: + SDL_Window* window{}; + SDL_Renderer* renderer{}; + SDL_Texture* texture{}; +};