summary refs log tree commit diff
path: root/src/GFX
diff options
context:
space:
mode:
Diffstat (limited to 'src/GFX')
-rw-r--r--src/GFX/Binder.cpp65
-rw-r--r--src/GFX/Binder.hpp38
-rw-r--r--src/GFX/Camera.cpp42
-rw-r--r--src/GFX/Camera.hpp26
-rw-r--r--src/GFX/Image/PPMParser.cpp154
-rw-r--r--src/GFX/Image/PPMParser.hpp44
-rw-r--r--src/GFX/Image/RawImage.cpp29
-rw-r--r--src/GFX/Image/RawImage.hpp37
-rw-r--r--src/GFX/Mesh.cpp29
-rw-r--r--src/GFX/Mesh.hpp34
-rw-r--r--src/GFX/Mouse.cpp24
-rw-r--r--src/GFX/Mouse.hpp21
-rw-r--r--src/GFX/Shading/Program.cpp42
-rw-r--r--src/GFX/Shading/Program.hpp25
-rw-r--r--src/GFX/Shading/Shader.cpp24
-rw-r--r--src/GFX/Shading/Shader.hpp29
-rw-r--r--src/GFX/Shading/Uniform.cpp14
-rw-r--r--src/GFX/Shading/Uniform.hpp23
-rw-r--r--src/GFX/Texture.cpp29
-rw-r--r--src/GFX/Texture.hpp17
-rw-r--r--src/GFX/Window.cpp55
-rw-r--r--src/GFX/Window.hpp30
22 files changed, 831 insertions, 0 deletions
diff --git a/src/GFX/Binder.cpp b/src/GFX/Binder.cpp
new file mode 100644
index 0000000..e7b7e4c
--- /dev/null
+++ b/src/GFX/Binder.cpp
@@ -0,0 +1,65 @@
+#include <GL/glew.h>
+#include "Binder.hpp"
+#include "Mesh.hpp"
+
+namespace MC::GFX {
+
+BindableMesh Binder::load(Mesh& mesh) {
+    auto vao = create_vao();
+    if (mesh.indices_size() > 0) {
+        store_indices(mesh.raw_indices(), mesh.indices_size());
+    }
+    store_in_attribute_list(0, 3, mesh.raw(), mesh.size() * 3);
+    store_in_attribute_list(1, 2, mesh.raw_tex_coords(), mesh.tex_coords_size() * 2);
+    unbind_vao();
+
+    return {vao, mesh.indices_size()};
+}
+
+uint32_t Binder::create_vao() {
+    GLuint vao;
+    glGenVertexArrays(1, &vao);
+    glBindVertexArray(vao);
+
+    return static_cast<uint32_t>(vao);
+}
+
+void Binder::unbind_vao() {
+    glBindVertexArray(0);
+}
+
+void Binder::store_indices(uint32_t* indices, size_t indices_size) {
+    GLuint ebo;
+    glGenBuffers(1, &ebo);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_size * sizeof(float), indices, GL_STATIC_DRAW);
+}
+
+void Binder::store_in_attribute_list(uint32_t attribute, size_t size, float* data, size_t data_size) {
+    GLuint vbo;
+    glGenBuffers(1, &vbo);
+
+    glBindBuffer(GL_ARRAY_BUFFER, vbo);
+    glBufferData(GL_ARRAY_BUFFER, data_size * sizeof(float), data, GL_STATIC_DRAW);
+    glVertexAttribPointer(attribute, size, GL_FLOAT, GL_FALSE, size * sizeof(float), nullptr);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+void BindableMesh::bind() const {
+    glBindVertexArray(m_vao);
+    glEnableVertexAttribArray(0);
+    glEnableVertexAttribArray(1);
+}
+
+void BindableMesh::unbind() {
+    glBindVertexArray(0);
+    glDisableVertexAttribArray(0);
+    glDisableVertexAttribArray(1);
+}
+
+size_t BindableMesh::size() const {
+    return m_vertex_count;
+}
+
+}
\ No newline at end of file
diff --git a/src/GFX/Binder.hpp b/src/GFX/Binder.hpp
new file mode 100644
index 0000000..99f9791
--- /dev/null
+++ b/src/GFX/Binder.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <cstdint>
+#include "Mesh.hpp"
+
+namespace MC::GFX {
+
+class BindableMesh {
+public:
+    void bind() const;
+    void unbind();
+
+    size_t size() const;
+
+private:
+    BindableMesh(uint32_t vao, size_t vertex_count) : m_vao(vao), m_vertex_count(vertex_count) {};
+
+    uint32_t m_vao;
+    size_t m_vertex_count;
+
+    friend class Binder;
+};
+
+class Binder {
+public:
+    Binder() = default;;
+
+    static BindableMesh load(Mesh& mesh);
+
+private:
+    static uint32_t create_vao();
+    static void unbind_vao();
+
+    static void store_in_attribute_list(uint32_t attribute, size_t size, float* data, size_t data_size);
+    static void store_indices(uint32_t* indices, size_t indices_size);
+};
+
+}
\ No newline at end of file
diff --git a/src/GFX/Camera.cpp b/src/GFX/Camera.cpp
new file mode 100644
index 0000000..6b25347
--- /dev/null
+++ b/src/GFX/Camera.cpp
@@ -0,0 +1,42 @@
+#include "Camera.hpp"
+
+namespace MC::GFX {
+
+Vector<3> Camera::position() {
+    return m_position;
+}
+
+void Camera::set_position(Vector<3> position) {
+    m_position = position;
+}
+
+void Camera::move(Vector<3> vector) {
+    m_position = m_position + vector;
+}
+
+void Camera::move_relative(Vector<3> by) {
+    auto rotation = Matrix<4, 4>::rotation(m_angles);
+
+    auto result = rotation.transpose() * Vector<4>{by.x(), by.y(), by.z(), 1.0f};
+    move(result.elements);
+}
+
+Rotation Camera::angles() {
+    return m_angles;
+}
+
+void Camera::set_angles(Rotation angles) {
+    m_angles = angles;
+}
+
+void Camera::rotate(Rotation by) {
+    m_angles = m_angles + by;
+
+    if (m_angles.pitch() > 89.0f) {
+        m_angles.pitch() = 89.0f;
+    } else if (m_angles.pitch() < -89.0f) {
+        m_angles.pitch() = -89.0f;
+    }
+}
+
+}
diff --git a/src/GFX/Camera.hpp b/src/GFX/Camera.hpp
new file mode 100644
index 0000000..f03f009
--- /dev/null
+++ b/src/GFX/Camera.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "../Math/Math.hpp"
+#include "../Math/Rotation.hpp"
+
+namespace MC::GFX {
+
+class Camera {
+public:
+    Camera() = default;
+
+    Vector<3> position();
+    void set_position(Vector<3> position);
+    void move(Vector<3> by);
+    void move_relative(Vector<3> by);
+
+    Rotation angles();
+    void set_angles(Rotation angles);
+    void rotate(Rotation by);
+
+private:
+    Vector<3> m_position = {};
+    Rotation m_angles = {};
+};
+
+}
\ No newline at end of file
diff --git a/src/GFX/Image/PPMParser.cpp b/src/GFX/Image/PPMParser.cpp
new file mode 100644
index 0000000..7fc2359
--- /dev/null
+++ b/src/GFX/Image/PPMParser.cpp
@@ -0,0 +1,154 @@
+#include <cctype>
+#include <string>
+#include "PPMParser.hpp"
+
+namespace MC::GFX::Image {
+
+RawImage PPMParser::parse() {
+    auto header = parse_header();
+
+    if (header.max_color != 255) {
+        throw std::logic_error("PPM max color values other than 255 are not implemented.");
+    }
+
+    if (header.type != P3) {
+        throw std::logic_error("Raw PPM not implemented.");
+    }
+
+    auto pixel_count = header.width * header.height;
+
+    RawImage image(pixel_count, header.width, header.height, 3);
+    for (uint64_t pixel_index = 0; pixel_index < pixel_count; pixel_index++) {
+        RawImage::Pixel pixel = parse_pixel(header.max_color);
+        image.add(pixel);
+    }
+
+    return image;
+}
+
+PPMParser::PPMHeader PPMParser::parse_header() {
+    PPMHeader header{};
+
+    skip_whitespace();
+
+    auto type_part = chomp_part();
+    skip_whitespace();
+
+    if (type_part == "P3") {
+        header.type = P3;
+    } else if (type_part == "P6") {
+        header.type = P6;
+    } else {
+        throw std::runtime_error("Unknown PPM type.");
+    }
+
+    auto width_part = chomp_number();
+    skip_whitespace();
+    header.width = width_part;
+
+    auto height_part = chomp_number();
+    skip_whitespace();
+    header.height = height_part;
+
+    auto max_color_part = chomp_number();
+    skip_whitespace();
+    header.max_color = max_color_part;
+
+    return header;
+}
+
+RawImage::Pixel PPMParser::parse_pixel(uint8_t max_color) {
+    auto r_sample = parse_sample();
+    auto g_sample = parse_sample();
+    auto b_sample = parse_sample();
+
+    if (r_sample > max_color || g_sample > max_color || b_sample > max_color) {
+        throw std::runtime_error("Sample can not be greater than Maxval.");
+    }
+
+    auto map_to_range = [=](uint64_t s) -> uint8_t { return (s * 255) / max_color; };
+
+    RawImage::Pixel pixel{};
+    pixel.r = map_to_range(r_sample);
+    pixel.g = map_to_range(g_sample);
+    pixel.b = map_to_range(b_sample);
+
+    return pixel;
+}
+
+uint64_t PPMParser::parse_sample() {
+    skip_whitespace();
+    auto sample = chomp_number();
+    skip_whitespace();
+    return sample;
+}
+
+uint64_t PPMParser::chomp_number() {
+    auto raw = chomp_part();
+
+    uint64_t number = 0;
+    for (uint8_t digit_ascii : raw) {
+        if (digit_ascii < '0' || digit_ascii > '9') {
+            throw std::runtime_error("Number contains non ASCII digits.");
+        }
+
+        uint8_t digit = digit_ascii - '0';
+
+        number *= 10;
+        number += digit;
+    }
+
+    return number;
+}
+
+std::string_view PPMParser::chomp_part() {
+    uint64_t length = 0;
+
+    while (!is_eof()) {
+        auto c = m_source[m_cursor + length];
+        if (std::isspace(c)) {
+            break;
+        }
+
+        length++;
+    }
+
+    auto part = m_source.substr(m_cursor, length);
+    m_cursor += length;
+
+    return part;
+}
+
+void PPMParser::skip_whitespace() {
+    uint8_t c;
+    while (!is_eof()) {
+        c = m_source[m_cursor];
+        if (c == '#') {
+            skip_comment();
+            continue;
+        }
+
+        if (!std::isspace(c)) {
+            break;
+        }
+
+        m_cursor++;
+    }
+}
+
+void PPMParser::skip_comment() {
+    uint8_t c = m_source[m_cursor];
+    if (c != '#') {
+        return;
+    }
+
+    while (c != '\n' && !is_eof()) {
+        c = m_source[++m_cursor];
+    }
+}
+
+bool PPMParser::is_eof() {
+    return m_cursor >= m_source.size();
+}
+
+}
\ No newline at end of file
diff --git a/src/GFX/Image/PPMParser.hpp b/src/GFX/Image/PPMParser.hpp
new file mode 100644
index 0000000..3909cee
--- /dev/null
+++ b/src/GFX/Image/PPMParser.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <cstdint>
+#include <string_view>
+#include "RawImage.hpp"
+
+namespace MC::GFX::Image {
+
+class PPMParser {
+public:
+    explicit PPMParser(std::string_view source) : m_source(source) {};
+
+    RawImage parse();
+private:
+    enum PPMType {
+        None,
+        P3,
+        P6
+    };
+
+    struct PPMHeader {
+        PPMType type;
+        uint32_t width;
+        uint32_t height;
+        uint8_t max_color;
+    };
+
+    PPMHeader parse_header();
+    RawImage::Pixel parse_pixel(uint8_t max_color);
+    uint64_t parse_sample();
+
+    uint64_t chomp_number();
+    std::string_view chomp_part();
+
+    void skip_whitespace();
+    void skip_comment();
+
+    bool is_eof();
+
+    std::string_view m_source;
+    uint64_t m_cursor = 0;
+};
+
+}
diff --git a/src/GFX/Image/RawImage.cpp b/src/GFX/Image/RawImage.cpp
new file mode 100644
index 0000000..aca8fbc
--- /dev/null
+++ b/src/GFX/Image/RawImage.cpp
@@ -0,0 +1,29 @@
+#include "RawImage.hpp"
+
+namespace MC::GFX::Image {
+
+void RawImage::add(RawImage::Pixel pixel) {
+    m_pixels.push_back(pixel);
+}
+
+size_t RawImage::size() const {
+    return m_pixels.size();
+}
+
+uint8_t* RawImage::raw() const {
+    return (uint8_t*)m_pixels.data();
+}
+
+uint32_t RawImage::width() const {
+    return m_width;
+}
+
+uint32_t RawImage::height() const {
+    return m_height;
+}
+
+uint8_t RawImage::channels() const {
+    return m_channels;
+}
+
+}
\ No newline at end of file
diff --git a/src/GFX/Image/RawImage.hpp b/src/GFX/Image/RawImage.hpp
new file mode 100644
index 0000000..916671b
--- /dev/null
+++ b/src/GFX/Image/RawImage.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <cstdint>
+#include <cstddef>
+#include <vector>
+
+namespace MC::GFX::Image {
+
+class RawImage {
+public:
+    RawImage() : m_pixels(), m_width(0), m_height(0), m_channels(0) {};
+
+    explicit RawImage(size_t pixel_count, uint32_t width, uint32_t height, uint8_t channels)
+        : m_pixels(), m_width(width), m_height(height), m_channels(channels) {
+        m_pixels.reserve(pixel_count);
+    }
+
+    struct Pixel {
+        uint8_t r, g, b;
+    };
+
+    void add(Pixel pixel);
+
+    size_t size() const;
+    uint8_t* raw() const;
+
+    uint32_t width() const;
+    uint32_t height() const;
+    uint8_t channels() const;
+private:
+    std::vector<Pixel> m_pixels;
+
+    uint32_t m_width, m_height;
+    uint8_t m_channels;
+};
+
+}
diff --git a/src/GFX/Mesh.cpp b/src/GFX/Mesh.cpp
new file mode 100644
index 0000000..12f8aaa
--- /dev/null
+++ b/src/GFX/Mesh.cpp
@@ -0,0 +1,29 @@
+#include "Mesh.hpp"
+
+namespace MC::GFX {
+
+float* Mesh::raw() {
+    return (float*) m_positions.data();
+}
+
+size_t Mesh::size() {
+    return m_positions.size();
+}
+
+uint32_t* Mesh::raw_indices() {
+    return m_indices.data();
+}
+
+size_t Mesh::indices_size() {
+    return m_indices.size();
+}
+
+float* Mesh::raw_tex_coords() {
+    return (float*) m_tex_coords.data();
+}
+
+size_t Mesh::tex_coords_size() {
+    return m_tex_coords.size();
+}
+
+}
\ No newline at end of file
diff --git a/src/GFX/Mesh.hpp b/src/GFX/Mesh.hpp
new file mode 100644
index 0000000..f027c8c
--- /dev/null
+++ b/src/GFX/Mesh.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <utility>
+#include <vector>
+#include <cstdint>
+#include "../Math/Math.hpp"
+
+namespace MC::GFX {
+
+class Mesh {
+public:
+    Mesh(std::vector<Vector<3>> positions, std::vector<Vector<2>> tex_coords, std::vector<uint32_t> indices)
+        : m_positions(std::move(positions)), m_tex_coords(std::move(tex_coords)), m_indices(std::move(indices)) {};
+
+    Mesh(std::vector<Vector<3>> positions, std::vector<Vector<2>> tex_coords)
+        : m_positions(std::move(positions)), m_tex_coords(std::move(tex_coords)), m_indices() {};
+
+    float* raw();
+    size_t size();
+
+    uint32_t* raw_indices();
+    size_t indices_size();
+
+    float* raw_tex_coords();
+    size_t tex_coords_size();
+
+private:
+    std::vector<Vector<3>> m_positions;
+    std::vector<Vector<2>> m_tex_coords;
+    std::vector<uint32_t> m_indices;
+
+};
+
+}
\ No newline at end of file
diff --git a/src/GFX/Mouse.cpp b/src/GFX/Mouse.cpp
new file mode 100644
index 0000000..5cd2698
--- /dev/null
+++ b/src/GFX/Mouse.cpp
@@ -0,0 +1,24 @@
+#include "Mouse.hpp"
+
+namespace MC::GFX {
+
+Vector<2> Mouse::update(GLFWwindow* window) {
+    double x, y;
+    glfwGetCursorPos(window, &x, &y);
+
+    if (m_first_event) {
+        m_last_x = x;
+        m_last_y = y;
+
+        m_first_event = false;
+    }
+
+    Vector<2> movement{static_cast<float>(x) - m_last_x,  static_cast<float>(y) - m_last_y};
+
+    m_last_x = x;
+    m_last_y = y;
+
+    return movement;
+}
+
+}
\ No newline at end of file
diff --git a/src/GFX/Mouse.hpp b/src/GFX/Mouse.hpp
new file mode 100644
index 0000000..3ed57a2
--- /dev/null
+++ b/src/GFX/Mouse.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <cstdint>
+#include <GLFW/glfw3.h>
+#include "../Math/Vector.hpp"
+
+namespace MC::GFX {
+
+class Mouse {
+public:
+    Mouse() = default;
+
+    Vector<2> update(GLFWwindow* window);
+private:
+    bool m_first_event = true;
+
+    float m_last_x = 0.0f;
+    float m_last_y = 0.0f;
+};
+
+}
\ No newline at end of file
diff --git a/src/GFX/Shading/Program.cpp b/src/GFX/Shading/Program.cpp
new file mode 100644
index 0000000..39393f8
--- /dev/null
+++ b/src/GFX/Shading/Program.cpp
@@ -0,0 +1,42 @@
+#include <GL/glew.h>
+#include <stdexcept>
+#include "Program.hpp"
+
+namespace MC::GFX::Shading {
+
+Program::Program(Shader fragment, Shader vertex) {
+    m_program = glCreateProgram();
+
+    glAttachShader(m_program, fragment.get());
+    glAttachShader(m_program, vertex.get());
+
+    glLinkProgram(m_program);
+
+    glDeleteShader(fragment.get());
+    glDeleteShader(vertex.get());
+
+    GLint success;
+    glGetProgramiv(m_program, GL_LINK_STATUS, &success);
+    if(!success) {
+        char message[512] = {};
+        glGetProgramInfoLog(m_program, 512, nullptr, message);
+
+        throw std::runtime_error(message);
+    }
+}
+
+void Program::bind() const {
+    glUseProgram(m_program);
+}
+
+Uniform Program::uniform(const std::string& name) const {
+    auto index = glGetUniformLocation(m_program, name.c_str());
+
+    return {name, static_cast<uint32_t>(index)};
+}
+
+uint32_t Program::get() const {
+    return m_program;
+}
+
+}
diff --git a/src/GFX/Shading/Program.hpp b/src/GFX/Shading/Program.hpp
new file mode 100644
index 0000000..15c9899
--- /dev/null
+++ b/src/GFX/Shading/Program.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include "Shader.hpp"
+#include "../../Math/Math.hpp"
+#include "Uniform.hpp"
+
+namespace MC::GFX::Shading {
+
+class Program {
+public:
+    Program(Shader fragment, Shader vertex);
+
+    uint32_t get() const;
+
+    Uniform uniform(const std::string& name) const;
+
+    void bind() const;
+
+private:
+    uint32_t m_program;
+};
+
+}
\ No newline at end of file
diff --git a/src/GFX/Shading/Shader.cpp b/src/GFX/Shading/Shader.cpp
new file mode 100644
index 0000000..ff954a5
--- /dev/null
+++ b/src/GFX/Shading/Shader.cpp
@@ -0,0 +1,24 @@
+#include <GL/glew.h>
+#include <stdexcept>
+#include "Shader.hpp"
+
+namespace MC::GFX::Shading {
+
+Shader::Shader(uint32_t type, const char* source) {
+    m_shader = glCreateShader(type);
+
+    glShaderSource(m_shader, 1, &source, nullptr);
+    glCompileShader(m_shader);
+
+    GLint success;
+    glGetShaderiv(m_shader, GL_COMPILE_STATUS, &success);
+    if(!success) {
+        char message[512] = {};
+        glGetShaderInfoLog(m_shader, 512, nullptr, message);
+
+        throw std::runtime_error(message);
+    }
+}
+
+
+}
\ No newline at end of file
diff --git a/src/GFX/Shading/Shader.hpp b/src/GFX/Shading/Shader.hpp
new file mode 100644
index 0000000..4a3d9cf
--- /dev/null
+++ b/src/GFX/Shading/Shader.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstdint>
+#include "../../Assets.hpp"
+
+namespace MC::GFX::Shading {
+
+class Shader {
+
+public:
+    uint32_t get() const {
+        return m_shader;
+    }
+
+    static Shader create_vertex() {
+        return {GL_VERTEX_SHADER, Assets::Shaders::vertex};
+    }
+
+    static Shader create_fragment() {
+        return {GL_FRAGMENT_SHADER, Assets::Shaders::fragment};
+    }
+
+private:
+    Shader(uint32_t type, const char* source);
+
+    uint32_t m_shader;
+};
+
+}
\ No newline at end of file
diff --git a/src/GFX/Shading/Uniform.cpp b/src/GFX/Shading/Uniform.cpp
new file mode 100644
index 0000000..9448574
--- /dev/null
+++ b/src/GFX/Shading/Uniform.cpp
@@ -0,0 +1,14 @@
+#include <GL/glew.h>
+#include "Uniform.hpp"
+
+namespace MC::GFX::Shading {
+
+void Uniform::set(Matrix<4, 4> value) const {
+    glUniformMatrix4fv(m_index, 1, GL_TRUE, value.elements);
+}
+
+void Uniform::set(Vector<3> value) const {
+    glUniform3f(m_index, value.x(), value.y(), value.z());
+}
+
+}
diff --git a/src/GFX/Shading/Uniform.hpp b/src/GFX/Shading/Uniform.hpp
new file mode 100644
index 0000000..8035dfe
--- /dev/null
+++ b/src/GFX/Shading/Uniform.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include "../../Math/Math.hpp"
+
+namespace MC::GFX::Shading {
+
+class Uniform {
+public:
+    Uniform(std::string name, uint32_t index)
+        : m_name(std::move(name)), m_index(index) {};
+
+    void set(Matrix<4, 4> value) const;
+    void set(Vector<3> value) const;
+
+private:
+    std::string m_name;
+    uint32_t m_index;
+};
+
+}
diff --git a/src/GFX/Texture.cpp b/src/GFX/Texture.cpp
new file mode 100644
index 0000000..1942128
--- /dev/null
+++ b/src/GFX/Texture.cpp
@@ -0,0 +1,29 @@
+#include <GL/glew.h>
+#include "Texture.hpp"
+
+namespace MC::GFX {
+
+Texture::Texture(const Image::RawImage& image) {
+    glGenTextures(1, &m_texture);
+    glBindTexture(GL_TEXTURE_2D, m_texture);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.raw());
+    glGenerateMipmap(GL_TEXTURE_2D);
+
+    glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+void Texture::bind() {
+    glBindTexture(GL_TEXTURE_2D, m_texture);
+}
+
+void Texture::unbind() {
+    glBindTexture(GL_TEXTURE_2D, 0);
+}
+}
\ No newline at end of file
diff --git a/src/GFX/Texture.hpp b/src/GFX/Texture.hpp
new file mode 100644
index 0000000..ff86634
--- /dev/null
+++ b/src/GFX/Texture.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "Image/RawImage.hpp"
+
+namespace MC::GFX {
+
+class Texture {
+public:
+    explicit Texture(const Image::RawImage& image);
+
+    void bind();
+    void unbind();
+private:
+    uint32_t m_texture;
+};
+
+}
\ No newline at end of file
diff --git a/src/GFX/Window.cpp b/src/GFX/Window.cpp
new file mode 100644
index 0000000..0a1828c
--- /dev/null
+++ b/src/GFX/Window.cpp
@@ -0,0 +1,55 @@
+#include <stdexcept>
+#include "Window.hpp"
+
+namespace MC::GFX {
+
+Window::Window(const char *title, uint32_t width, uint32_t height) {
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+    glfwWindowHint(GLFW_DOUBLEBUFFER, GL_TRUE);
+
+    m_window = glfwCreateWindow(width, height, title, nullptr, nullptr);
+    if (m_window == nullptr) {
+        throw std::runtime_error("Failed to create window.");
+    }
+
+    glfwMakeContextCurrent(m_window);
+    glfwSetInputMode(m_window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
+}
+
+Window::~Window() {
+    glfwDestroyWindow(m_window);
+}
+
+bool Window::should_close() {
+    return glfwWindowShouldClose(m_window);
+}
+
+GLFWwindow* Window::get() {
+    return m_window;
+}
+
+void Window::close() {
+    glfwSetWindowShouldClose(m_window, true);
+}
+
+Vector<2> Window::mouse_delta() {
+    return m_mouse.update(m_window);
+}
+
+bool Window::key(int key, int type) {
+    return (glfwGetKey(m_window, key) == type);
+}
+
+void Window::start_frame() {
+    glfwSwapBuffers(m_window);
+    glfwPollEvents();
+}
+
+void Window::on_size_change(void (callback)(GLFWwindow*, int, int)) {
+    glfwSetFramebufferSizeCallback(m_window, static_cast<GLFWframebuffersizefun>(callback));
+}
+
+}
\ No newline at end of file
diff --git a/src/GFX/Window.hpp b/src/GFX/Window.hpp
new file mode 100644
index 0000000..63e8446
--- /dev/null
+++ b/src/GFX/Window.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <cstdint>
+#include <GLFW/glfw3.h>
+#include "../Math/Vector.hpp"
+#include "Mouse.hpp"
+
+namespace MC::GFX {
+
+class Window {
+public:
+    Window(const char* title, uint32_t width, uint32_t height);
+    ~Window();
+
+    GLFWwindow* get();
+
+    void on_size_change(void (* callback)(GLFWwindow*, int, int));
+
+    void close();
+    void start_frame();
+    Vector<2> mouse_delta();
+
+    bool key(int key, int type);
+    bool should_close();
+private:
+    GLFWwindow* m_window;
+    Mouse m_mouse;
+};
+
+}
\ No newline at end of file