summaryrefslogtreecommitdiff
path: root/src/GFX
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-10-21 01:03:18 +0200
committerMel <einebeere@gmail.com>2022-10-21 01:03:18 +0200
commit6ed978051668c08f5a957c97570f364dd580c807 (patch)
treee3db93c52fcd86e26bc859d46e755290d2a7f40c /src/GFX
parent0464a83dfaebaa75d6e2d3b7431e84ebd83fccfd (diff)
downloadmeowcraft-6ed978051668c08f5a957c97570f364dd580c807.tar.zst
meowcraft-6ed978051668c08f5a957c97570f364dd580c807.zip
Namespace and Folder refactor
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