diff --git a/assets/shaders/canvas_fragment.glsl b/assets/shaders/canvas_fragment.glsl new file mode 100644 index 0000000..c90bd84 --- /dev/null +++ b/assets/shaders/canvas_fragment.glsl @@ -0,0 +1,8 @@ +#version 130 + +in vec4 i_color; +out vec4 o_color; + +void main() { + o_color = i_color; +} diff --git a/assets/shaders/canvas_vertex.glsl b/assets/shaders/canvas_vertex.glsl new file mode 100644 index 0000000..75de885 --- /dev/null +++ b/assets/shaders/canvas_vertex.glsl @@ -0,0 +1,13 @@ +#version 130 + +in vec2 pos; +in vec4 color; + +out vec4 i_color; + +uniform mat4 proj; + +void main() { + gl_Position = proj * vec4(pos, 0.0, 1.0); + i_color = color; +} diff --git a/meson.build b/meson.build index 8457095..0d0829a 100644 --- a/meson.build +++ b/meson.build @@ -22,8 +22,23 @@ executable( 'bloody-gun', sources: [ 'src/main.cpp', + + # core + 'src/Game.cpp', + 'src/BaseScene.cpp', + 'src/scenes/World.cpp', + + # gfx + 'src/Renderer.cpp', + 'src/Shaders.cpp', + 'src/Canvas.cpp', + + ], dependencies: [ + dependency('glew'), + dependency('glm'), + dependency('sdl2'), ], install: true ) diff --git a/src/BaseScene.cpp b/src/BaseScene.cpp new file mode 100644 index 0000000..3582589 --- /dev/null +++ b/src/BaseScene.cpp @@ -0,0 +1,13 @@ +#include "BaseScene.hpp" + +namespace bg +{ + /*explicit*/ BaseScene::BaseScene(Game& game) + : m_game { game } + { + } + + /*virtual*/ BaseScene::~BaseScene() + { + } +} diff --git a/src/BaseScene.hpp b/src/BaseScene.hpp new file mode 100644 index 0000000..af4eb06 --- /dev/null +++ b/src/BaseScene.hpp @@ -0,0 +1,24 @@ +#ifndef bg_BASESCENE_HPP +#define bg_BASESCENE_HPP + +#include "commons.hpp" +#include "Renderer.hpp" +#include "Game.hpp" + +namespace bg +{ + class BaseScene + { + public: + explicit BaseScene(Game& game); + virtual ~BaseScene(); + + virtual void update(float /*dt*/) {} + virtual void draw(Renderer const& /*renderer*/) {} + + private: + Game& m_game; + }; +} + +#endif diff --git a/src/Canvas.cpp b/src/Canvas.cpp new file mode 100644 index 0000000..4a38bee --- /dev/null +++ b/src/Canvas.cpp @@ -0,0 +1,98 @@ +#include "Canvas.hpp" + +namespace bg +{ + /*explicit*/ Canvas::Canvas() + { + glGenVertexArrays(1, &m_vao); + glBindVertexArray(m_vao); + + glGenBuffers(1, &m_vbo); + } + + /*virtual*/ Canvas::~Canvas() + { + glDeleteBuffers(1, &m_vbo); + glDeleteVertexArrays(1, &m_vao); + } + + void Canvas::use() const + { + m_shaders->use(); + glBindVertexArray(m_vao); + glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + } + + void Canvas::draw(glm::mat4 const& transform) const + { + use(); + m_shaders->set_matrix("proj", transform); + glDrawArrays(GL_TRIANGLES, 0, m_vertices.size()); + } + + void Canvas::clear() + { + m_vertices.clear(); + update(); + } + + void Canvas::rect(glm::vec2 const& pos, + glm::vec2 const& size, + glm::vec4 const& color) + { + // A -- B + // | | + // D -- C + + Vertex a {pos.x - size.x/2.0f, pos.y - size.y/2.0f, + color.x, color.y, color.z, color.w + }; + + Vertex b {pos.x + size.x/2.0f, pos.y - size.y/2.0f, + color.x, color.y, color.z, color.w}; + + Vertex c {pos.x + size.x/2.0f, pos.y + size.y/2.0f, + color.x, color.y, color.z, color.w}; + + Vertex d {pos.x - size.x/2.0f, pos.y + size.y/2.0f, + color.x, color.y, color.z, color.w}; + + m_vertices.push_back(a); + m_vertices.push_back(b); + m_vertices.push_back(c); + + m_vertices.push_back(a); + m_vertices.push_back(c); + m_vertices.push_back(d); + + update(); + } + + void Canvas::update() + { + use(); + + glBufferData(GL_ARRAY_BUFFER, + m_vertices.size() * sizeof(Vertex), + m_vertices.data(), + GL_STATIC_DRAW); + + GLint pos = m_shaders->attrib("pos"); + glEnableVertexAttribArray(pos); + glVertexAttribPointer(pos, + 2, + GL_FLOAT, + GL_FALSE, + sizeof(Vertex), + nullptr); + + GLint color = m_shaders->attrib("color"); + glEnableVertexAttribArray(color); + glVertexAttribPointer(color, + 4, + GL_FLOAT, + GL_FALSE, + sizeof(Vertex), + reinterpret_cast(offsetof(Vertex, r))); + } +} diff --git a/src/Canvas.hpp b/src/Canvas.hpp new file mode 100644 index 0000000..b8bcc41 --- /dev/null +++ b/src/Canvas.hpp @@ -0,0 +1,48 @@ +#ifndef bg_CANVAS_HPP +#define bg_CANVAS_HPP + +#include +#include +#include "Shaders.hpp" +#include "commons.hpp" + +namespace bg +{ + struct Vertex + { + float x; float y; + float r; float g; float b; float a; + }; + + class Canvas + { + public: + explicit Canvas(); + virtual ~Canvas(); + + void use() const; + void draw(glm::mat4 const& transform) const; + + // Draw shapes + // ----------- + void clear(); + + void rect(glm::vec2 const& pos, + glm::vec2 const& size, + glm::vec4 const& color = + glm::vec4 {0.0f, 0.0f, 0.0f, 1.0f}); + + private: + std::shared_ptr m_shaders = + std::make_shared(); + + GLuint m_vao; + GLuint m_vbo; + std::vector m_vertices; + + void update(); + + }; +} + +#endif diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..c18ca7d --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,122 @@ +#include "Game.hpp" +#include "BaseScene.hpp" + +namespace bg +{ + /*explicit*/ Game::Game() + { + } + + /*virtual*/ Game::~Game() + { + free(); + } + + void Game::init() + { + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + throw game_error {"Cannot initialize SDL 2."}; + } + + auto title = "Bloody Gun v" + BG_VERSION; + + m_window = SDL_CreateWindow(title.c_str(), + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 1600, 900, + SDL_WINDOW_SHOWN + | SDL_WINDOW_FULLSCREEN + | SDL_WINDOW_OPENGL); + + if (!m_window) + { + throw game_error {"Cannot open SDL window."}; + } + + + { + int w; + int h; + SDL_GetWindowSize(m_window, &w, &h); + m_renderer = std::make_unique(w, h); + } + + m_context = SDL_GL_CreateContext(m_window); + + if (!m_context) + { + throw game_error {std::string() + + "Cannot create SDL 2 GL context (" + + SDL_GetError() + + ")"}; + } + + if (glewInit() != GLEW_OK) + { + throw game_error {"Cannot initialize glew."}; + } + } + + void Game::free() + { + SDL_GL_DeleteContext(m_context); + SDL_DestroyWindow(m_window); + m_window = nullptr; + SDL_Quit(); + } + + int Game::start() + { + SDL_Event event; + + m_running = true; + float dt = 0.0f; + + while (m_running) + { + auto loop_start = std::chrono::steady_clock::now(); + + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + m_running = false; + } + } + + if (m_scene_stack.empty() == false) + { + m_scene_stack.back()->update(dt); + } + + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + + if (m_scene_stack.empty() == false) + { + m_scene_stack.back()->draw(*m_renderer); + } + + SDL_GL_SwapWindow(m_window); + + for (auto& scene: m_push_queue) + { + m_scene_stack.push_back(std::move(scene)); + } + + m_push_queue.clear(); + + auto elapsed = std::chrono::steady_clock::now() - loop_start; + dt = std::chrono::duration_cast(elapsed) + .count() / 1000000.0f; + } + + return 0; + } + + void Game::queue_push(std::unique_ptr scene) + { + m_push_queue.push_back(std::move(scene)); + } +} diff --git a/src/Game.hpp b/src/Game.hpp new file mode 100644 index 0000000..a1b8855 --- /dev/null +++ b/src/Game.hpp @@ -0,0 +1,43 @@ +#ifndef bg_GAME_HPP +#define bg_GAME_HPP + +#include "commons.hpp" + +#include +#include + +#include "Renderer.hpp" + +namespace bg +{ + BG_ERROR(game_error); + + class BaseScene; + + class Game + { + public: + explicit Game(); + virtual ~Game(); + + // Lifecycle management + // -------------------- + void init(); + void free(); + int start(); + + // Scenes + // ------ + void queue_push(std::unique_ptr scene); + + private: + bool m_running = false; + SDL_Window* m_window = nullptr; + std::unique_ptr m_renderer; + SDL_GLContext m_context = nullptr; + std::vector> m_scene_stack; + std::vector> m_push_queue; + }; +} + +#endif diff --git a/src/Renderer.cpp b/src/Renderer.cpp new file mode 100644 index 0000000..a9903e1 --- /dev/null +++ b/src/Renderer.cpp @@ -0,0 +1,26 @@ +#include +#include +#include "Renderer.hpp" +#include "Canvas.hpp" + +namespace bg +{ + /*explicit*/ Renderer::Renderer(int width, int height) + : m_width { width } + , m_height { height } + { + } + + /*virtual*/ Renderer::~Renderer() + { + } + + void Renderer::draw(Canvas const& canvas) const + { + glm::mat4 proj = glm::ortho(0.0f, + static_cast(m_width), + static_cast(m_height), + 0.0f); + canvas.draw(proj); + } +} diff --git a/src/Renderer.hpp b/src/Renderer.hpp new file mode 100644 index 0000000..b078229 --- /dev/null +++ b/src/Renderer.hpp @@ -0,0 +1,22 @@ +#ifndef bg_RENDERER_HPP +#define bg_RENDERER_HPP + +namespace bg +{ + class Canvas; + + class Renderer + { + public: + explicit Renderer(int width, int height); + virtual ~Renderer(); + + void draw(Canvas const& canvas) const; + + private: + int m_width; + int m_height; + }; +} + +#endif diff --git a/src/Shaders.cpp b/src/Shaders.cpp new file mode 100644 index 0000000..af6128c --- /dev/null +++ b/src/Shaders.cpp @@ -0,0 +1,124 @@ +#include +#include "Shaders.hpp" + +namespace bg +{ + /*explicit*/ Shaders::Shaders() + { + shader(BG_DATADIR / "assets" / "shaders" / "canvas_vertex.glsl", + GL_VERTEX_SHADER); + + shader(BG_DATADIR / "assets" / "shaders" / "canvas_fragment.glsl", + GL_FRAGMENT_SHADER); + + glLinkProgram(m_program); + + GLint status = GL_FALSE; + + glGetProgramiv(m_program, GL_LINK_STATUS, &status); + + if (!status) + { + size_t const SZ = 512; + char msg[SZ]; + glGetProgramInfoLog(m_program, SZ, nullptr, msg); + + throw shaders_error {std::string() + "Cannot link program.\n" + msg}; + } + } + + /*virtual*/ Shaders::~Shaders() + { + glDeleteProgram(m_program); + } + + void Shaders::use() const + { + glUseProgram(m_program); + } + + GLint Shaders::attrib(std::string const& name) const + { + use(); + + GLint loc = glGetAttribLocation(m_program, name.c_str()); + + if (loc < 0) + { + throw shaders_error {"Cannot find vertex attribute '"+name+"'."}; + } + + return loc; + } + + GLint Shaders::matrix(std::string const& name) const + { + use(); + + GLint loc = glGetUniformLocation(m_program, name.c_str()); + + if (loc < 0) + { + throw shaders_error {"Cannot find uniform '"+name+"'."}; + } + + return loc; + } + + void Shaders::set_matrix(std::string const& name, glm::mat4 mat) const + { + use(); + glUniformMatrix4fv(matrix(name), 1, GL_FALSE, glm::value_ptr(mat)); + } + + GLuint Shaders::shader(std::filesystem::path const& path, GLenum type) + { + if (!std::filesystem::is_regular_file(path)) + { + throw shaders_error {"Shader file '" + path.string() + "' not found."}; + } + + GLuint myshader = glCreateShader(type); + + std::string source; + + { + std::stringstream ss; + std::ifstream file { path }; + + if (!file) + { + throw shaders_error {"Cannot open file '" + path.string() + "'"}; + } + + ss << file.rdbuf(); + source = ss.str(); + } + + char const* sources[] = {source.c_str()}; + + glShaderSource(myshader, 1, sources, nullptr); + glCompileShader(myshader); + + GLint status = GL_FALSE; + + glGetShaderiv(myshader, GL_COMPILE_STATUS, &status); + + if (!status) + { + size_t const SZ = 512; + char msg[SZ]; + glGetShaderInfoLog(myshader, SZ, nullptr, msg); + + throw shaders_error {"Cannot compile shader '" + + path.string() + + "'\n" + + msg + }; + } + + glAttachShader(m_program, myshader); + + return myshader; + } +} diff --git a/src/Shaders.hpp b/src/Shaders.hpp new file mode 100644 index 0000000..c8bc8c6 --- /dev/null +++ b/src/Shaders.hpp @@ -0,0 +1,34 @@ +#ifndef bg_SHADERS_HPP +#define bg_SHADERS_HPP + +#include +#include +#include "commons.hpp" + +namespace bg +{ + BG_ERROR(shaders_error); + + class Shaders + { + public: + explicit Shaders(); + virtual ~Shaders(); + + void use() const; + + // Attribute and Uniforms + // ---------------------- + GLint attrib(std::string const& name) const; + GLint matrix(std::string const& name) const; + void set_matrix(std::string const& name, glm::mat4 mat) const; + + private: + GLuint m_program = glCreateProgram(); + + GLuint shader(std::filesystem::path const& path, GLenum type); + + }; +} + +#endif diff --git a/src/commons.hpp b/src/commons.hpp index 882e21f..620162b 100644 --- a/src/commons.hpp +++ b/src/commons.hpp @@ -1,7 +1,18 @@ #ifndef bg_COMMONS_HPP #define bg_COMMONS_HPP +#include +#include #include +#include +#include #include "conf.hpp" + +#define BG_ERROR(NAME) \ + struct NAME : public std::runtime_error { \ + explicit NAME (std::string const& what) \ + : std::runtime_error { what } {} \ + } + #endif diff --git a/src/main.cpp b/src/main.cpp index 1ab284f..cf9d9d3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,15 @@ #include #include "commons.hpp" +#include "Game.hpp" +#include "scenes/World.hpp" int main(int, char**) { - std::cout << "Bloody Gun v" << BG_VERSION << std::endl; - return 0; + bg::Game game; + game.init(); + + auto world = std::make_unique(game); + game.queue_push(std::move(world)); + + return game.start(); } diff --git a/src/scenes/World.cpp b/src/scenes/World.cpp new file mode 100644 index 0000000..5fb28a9 --- /dev/null +++ b/src/scenes/World.cpp @@ -0,0 +1,30 @@ +#include "World.hpp" +#include "../Renderer.hpp" + +namespace bg +{ + /*explicit*/ World::World(Game& game) + : BaseScene(game) + { + m_canvas.rect(glm::vec2 {64.0f, 64.0f}, + glm::vec2 {128.0f, 32.0f}, + glm::vec4 {0.0f, 0.5f, 0.0f, 1.0f}); + + m_canvas.rect(glm::vec2 {512.0f, 512.0f}, + glm::vec2 {96.0f, 96.0f}, + glm::vec4 {0.5f, 0.0f, 0.0f, 1.0f}); + } + + /*virtual*/ World::~World() + { + } + + void World::update(float) /*override*/ + { + } + + void World::draw(Renderer const& renderer) /*override*/ + { + renderer.draw(m_canvas); + } +} diff --git a/src/scenes/World.hpp b/src/scenes/World.hpp new file mode 100644 index 0000000..b249c38 --- /dev/null +++ b/src/scenes/World.hpp @@ -0,0 +1,24 @@ +#ifndef bg_WORLD_HPP +#define bg_WORLD_HPP + +#include "../commons.hpp" +#include "../BaseScene.hpp" +#include "../Canvas.hpp" + +namespace bg +{ + class World: public BaseScene + { + public: + explicit World(Game& game); + virtual ~World(); + + void update(float dt) override; + void draw(Renderer const& renderer) override; + + private: + Canvas m_canvas; + }; +} + +#endif