diff --git a/assets/shaders/fragment.glsl b/assets/shaders/fragment.glsl new file mode 100644 index 0000000..b03d730 --- /dev/null +++ b/assets/shaders/fragment.glsl @@ -0,0 +1,8 @@ +#version 130 + +in vec4 frag_icolor; +out vec4 frag_ocolor; + +void main() { + frag_ocolor = frag_icolor; +} diff --git a/assets/shaders/vertex.glsl b/assets/shaders/vertex.glsl new file mode 100644 index 0000000..2ef130e --- /dev/null +++ b/assets/shaders/vertex.glsl @@ -0,0 +1,13 @@ +#version 130 + +in vec3 pos; +in vec4 color; + +out vec4 frag_icolor; + +uniform mat4 mvp; + +void main() { + gl_Position = mvp * vec4(pos, 1.0f); + frag_icolor = color; +} diff --git a/meson.build b/meson.build index c1942ac..3cfd25d 100644 --- a/meson.build +++ b/meson.build @@ -7,8 +7,11 @@ project('duck2d', 'cpp_std=c++17', ]) +assets_dir = get_option('prefix') / get_option('datadir') / 'duck2d' + conf = configuration_data() conf.set('version', meson.project_version()) +conf.set('assets_dir', assets_dir / 'assets') configure_file( @@ -20,7 +23,23 @@ configure_file( executable('d2d', sources: [ 'src/main.cpp', + 'src/Game.cpp', + 'src/Script.cpp', + + # graphics + 'src/Shader.cpp', + 'src/Shape.cpp', + 'src/Material.cpp', + 'src/Object.cpp', + 'src/Renderer.cpp', + ], dependencies: [ + dependency('sdl2'), + dependency('glew'), + dependency('glm'), + dependency('guile-3.0'), ], install: true) + +install_subdir('assets', install_dir: assets_dir) diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..a82c77a --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,78 @@ +#include +#include "Game.hpp" +#include + +namespace d2 +{ + /*explicit*/ Game::Game(std::string const& title, + unsigned width, + unsigned height) + { + SDL_Init(SDL_INIT_VIDEO); + + m_window = SDL_CreateWindow(title.c_str(), + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + width, height, + SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL); + if (!m_window) + { + throw game_error {"Cannot open a new window."}; + } + + m_context = SDL_GL_CreateContext(m_window); + + if (!m_context) + { + throw game_error {"Cannot initialize opengl context."}; + } + + if (glewInit() != GLEW_OK) + { + throw game_error {"Cannot initialize GLEW."}; + } + + m_renderer = std::make_unique(width, height); + } + + /*virtual*/ Game::~Game() + { + SDL_GL_DeleteContext(m_context); m_context = nullptr; + SDL_DestroyWindow(m_window); m_window = nullptr; + SDL_Quit(); + } + + void Game::run() + { + SDL_Event event; + bool m_running = true; + + while (m_running) + { + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + m_running = false; + } + } + + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + for (auto& obj: m_draw_queue) + { + m_renderer->draw(*obj); + } + + //m_draw_queue.clear(); + + SDL_GL_SwapWindow(m_window); + } + } + + void Game::queue_draw(std::unique_ptr obj) + { + m_draw_queue.push_back(std::move(obj)); + } +} diff --git a/src/Game.hpp b/src/Game.hpp new file mode 100644 index 0000000..83958e6 --- /dev/null +++ b/src/Game.hpp @@ -0,0 +1,31 @@ +#ifndef d2_GAME_HPP +#define d2_GAME_HPP + +#include +#include "commons.hpp" +#include "Renderer.hpp" + +namespace d2 +{ + D2_ERROR(game_error); + + class Game + { + public: + explicit Game(std::string const& title, + unsigned width, + unsigned height); + virtual ~Game(); + + void run(); + void queue_draw(std::unique_ptr obj); + + private: + SDL_Window* m_window = nullptr; + SDL_GLContext m_context; + std::unique_ptr m_renderer; + std::vector> m_draw_queue; + }; +} + +#endif diff --git a/src/Material.cpp b/src/Material.cpp new file mode 100644 index 0000000..fe0e337 --- /dev/null +++ b/src/Material.cpp @@ -0,0 +1,20 @@ +#include "Material.hpp" + +namespace d2 +{ + /*explicit*/ Material::Material() + { + } + + /*virtual*/ Material::~Material() + { + } + + void Material::set_color(unsigned r, unsigned g, unsigned b, unsigned a) + { + m_r = r; + m_g = g; + m_b = b; + m_a = a; + } +} diff --git a/src/Material.hpp b/src/Material.hpp new file mode 100644 index 0000000..e0f7e51 --- /dev/null +++ b/src/Material.hpp @@ -0,0 +1,29 @@ +#ifndef d2_MATERIAL_HPP +#define d2_MATERIAL_HPP + +#include "commons.hpp" + +namespace d2 +{ + class Material + { + public: + explicit Material(); + virtual ~Material(); + + void set_color(unsigned r, unsigned g, unsigned b, unsigned a); + + unsigned r() const { return m_r; } + unsigned g() const { return m_g; } + unsigned b() const { return m_b; } + unsigned a() const { return m_a; } + + private: + unsigned m_r = 0; + unsigned m_g = 0; + unsigned m_b = 0; + unsigned m_a = 0; + }; +} + +#endif diff --git a/src/Object.cpp b/src/Object.cpp new file mode 100644 index 0000000..108a345 --- /dev/null +++ b/src/Object.cpp @@ -0,0 +1,108 @@ +#include "Object.hpp" + +namespace d2 +{ + /*explicit*/ Object::Object(std::shared_ptr shape, + std::shared_ptr material, + std::shared_ptr shader) + : m_shape { shape } + , m_material { material } + , m_shader { shader } + { + glGenVertexArrays(1, &m_vao); + use(); + glGenBuffers(BUFFER_COUNT, m_vbos); + + update(); + } + + /*virtual*/ Object::~Object() + { + glDeleteBuffers(BUFFER_COUNT, m_vbos); + glDeleteVertexArrays(1, &m_vao); + } + + void Object::set_position(glm::vec3 position) + { + m_position = position; + } + + void Object::set_size(glm::vec3 size) + { + m_size = size; + } + + void Object::use() const + { + m_shader->use(); + glBindVertexArray(m_vao); + } + + void Object::update() const + { + update_pos(); + update_color(); + } + + size_t Object::point_count() const + { + return m_shape->points().size(); + } + + void Object::update_pos() const + { + use(); + + glBindBuffer(GL_ARRAY_BUFFER, m_vbos[POS_BUFFER]); + auto const& vertices = m_shape->points(); + + glBufferData(GL_ARRAY_BUFFER, + vertices.size() * sizeof(glm::vec3), + vertices.data(), + GL_STATIC_DRAW); + + GLint location = m_shader->attr("pos"); + assert(location >= 0); + + glEnableVertexAttribArray(location); + glVertexAttribPointer(location, + 3, + GL_FLOAT, + GL_FALSE, + sizeof(glm::vec3), + nullptr); + } + + void Object::update_color() const + { + use(); + + glBindBuffer(GL_ARRAY_BUFFER, m_vbos[COLOR_BUFFER]); + std::vector colors; + + for (size_t i=0; ipoints().size(); i++) + { + colors.push_back(glm::vec4 + {static_cast(m_material->r())/255.0f, + static_cast(m_material->g()/255.0f), + static_cast(m_material->b()/255.0f), + static_cast(m_material->a())/255.0f}); + } + + glBufferData(GL_ARRAY_BUFFER, + colors.size() * sizeof(glm::vec4), + colors.data(), + GL_STATIC_DRAW); + + GLint location = m_shader->attr("color"); + assert(location >= 0); + + glEnableVertexAttribArray(location); + glVertexAttribPointer(location, + 4, + GL_FLOAT, + GL_FALSE, + sizeof(glm::vec4), + nullptr); + } +} diff --git a/src/Object.hpp b/src/Object.hpp new file mode 100644 index 0000000..6e5696d --- /dev/null +++ b/src/Object.hpp @@ -0,0 +1,59 @@ +#ifndef d2_OBJECT_HPP +#define d2_OBJECT_HPP + +#include +#include +#include +#include "commons.hpp" + +#include "Shader.hpp" +#include "Material.hpp" +#include "Shape.hpp" + +namespace d2 +{ + enum BufferType { + POS_BUFFER, + COLOR_BUFFER, + BUFFER_COUNT + }; + + class Object + { + public: + explicit Object(std::shared_ptr shape, + std::shared_ptr material, + std::shared_ptr shader); + + virtual ~Object(); + + glm::mat4 transform() const { return m_transform; } + std::shared_ptr shader() const { return m_shader; } + + glm::vec3 position() const { return m_position; } + glm::vec3 size() const { return m_size; } + + void set_position(glm::vec3 position); + void set_size(glm::vec3 size); + + void use() const; + void update() const; + size_t point_count() const; + + private: + GLuint m_vao; + GLuint m_vbos[BUFFER_COUNT]; + glm::vec3 m_position; + glm::vec3 m_size; + + glm::mat4 m_transform; + std::shared_ptr m_shape; + std::shared_ptr m_material; + std::shared_ptr m_shader; + + void update_pos() const; + void update_color() const; + }; +} + +#endif diff --git a/src/Renderer.cpp b/src/Renderer.cpp new file mode 100644 index 0000000..ba23bf4 --- /dev/null +++ b/src/Renderer.cpp @@ -0,0 +1,34 @@ +#include "Renderer.hpp" + +namespace d2 +{ + /*explicit*/ Renderer::Renderer(unsigned width, unsigned height) + : m_width { width } + , m_height { height } + { + m_projection = glm::ortho(0.0f, + static_cast(m_width), + static_cast(height), + 0.0f); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + /*virtual*/ Renderer::~Renderer() + { + } + + void Renderer::draw(Object const& object) const + { + glm::mat4 mvp {1.0f}; + + mvp = glm::translate(mvp, object.position()); + mvp = glm::scale(mvp, object.size()); + mvp = m_projection * mvp; + + object.use(); + object.shader()->set_matrix("mvp", mvp); + + glDrawArrays(GL_TRIANGLES, 0, object.point_count()); + } +} diff --git a/src/Renderer.hpp b/src/Renderer.hpp new file mode 100644 index 0000000..b66cd3d --- /dev/null +++ b/src/Renderer.hpp @@ -0,0 +1,26 @@ +#ifndef d2_RENDERER_HPP +#define d2_RENDERER_HPP + +#include +#include +#include "commons.hpp" +#include "Object.hpp" + +namespace d2 +{ + class Renderer + { + public: + explicit Renderer(unsigned width, unsigned height); + virtual ~Renderer(); + + void draw(Object const& object) const; + + private: + unsigned m_width; + unsigned m_height; + glm::mat4 m_projection; + }; +} + +#endif diff --git a/src/Script.cpp b/src/Script.cpp new file mode 100644 index 0000000..68b75dc --- /dev/null +++ b/src/Script.cpp @@ -0,0 +1,85 @@ +#include "Script.hpp" + +namespace d2 +{ + /*static*/ std::unique_ptr Script::game = nullptr; + /*static*/ glm::vec4 Script::color = {0.0f, 0.0f, 0.0f, 0.0f}; + + /*static*/ void Script::init() + { + scm_init_guile(); + scm_c_define_gsubr("winconf", 3, 0, 0, (void*) &fn_winconf); + + // Drawing + // ------- + scm_c_define_gsubr("rect", 4, 0, 0, (void*) &fn_rect); + scm_c_define_gsubr("color!", 4, 0, 0, (void*) &fn_set_color); + } + + /*static*/ void Script::run(std::filesystem::path conf_file) + { + scm_c_primitive_load(conf_file.c_str()); + + if (Script::game == nullptr) + { + Script::game = std::make_unique("Duck2D", 640, 480); + } + + Script::game->run(); + } + + /*static*/ SCM Script::fn_winconf(SCM scm_title, + SCM scm_width, + SCM scm_height) + { + std::string title = scm_to_locale_string(scm_title); + unsigned width = scm_to_uint32(scm_width); + unsigned height = scm_to_uint32(scm_height); + + Script::game = std::make_unique(title, width, height); + + return SCM_BOOL_T; + } + + /*static*/ SCM Script::fn_rect(SCM scm_x, SCM scm_y, SCM scm_w, SCM scm_h) + { + float x = scm_to_double(scm_x); + float y = scm_to_double(scm_y); + float w = scm_to_double(scm_w); + float h = scm_to_double(scm_h); + + auto shape = std::make_shared(); + shape->set_rect(); + + auto material = std::make_shared(); + + material->set_color(Script::color.x, + Script::color.y, + Script::color.z, + Script::color.w); + + auto shader = std::make_shared(); + + auto obj = std::make_unique(shape, material, shader); + obj->set_position(glm::vec3 {x, y, 0.0f}); + obj->set_size(glm::vec3 {w, h, 0.0f}); + + Script::game->queue_draw(std::move(obj)); + + return SCM_BOOL_T; + } + + /*static*/ + SCM Script::fn_set_color(SCM scm_r, SCM scm_g, SCM scm_b, SCM scm_a) + { + float r = scm_to_double(scm_r); + float g = scm_to_double(scm_g); + float b = scm_to_double(scm_b); + float a = scm_to_double(scm_a); + + Script::color = glm::vec4 {r, g, b, a}; + + return SCM_BOOL_T; + } + +} diff --git a/src/Script.hpp b/src/Script.hpp new file mode 100644 index 0000000..9908a48 --- /dev/null +++ b/src/Script.hpp @@ -0,0 +1,25 @@ +#ifndef d2_SCRIPT_HPP +#define d2_SCRIPT_HPP +#include + +#include "commons.hpp" +#include "Game.hpp" + +namespace d2 +{ + struct Script + { + static void init(); + static void run(std::filesystem::path conf_file); + + static SCM fn_winconf(SCM scm_title, SCM scm_width, SCM scm_height); + static SCM fn_rect(SCM scm_x, SCM scm_y, SCM scm_w, SCM scm_h); + static SCM fn_set_color(SCM scm_r, SCM scm_g, SCM scm_b, SCM scm_a); + + private: + static std::unique_ptr game; + static glm::vec4 color; + }; +} + +#endif diff --git a/src/Shader.cpp b/src/Shader.cpp new file mode 100644 index 0000000..b3677a1 --- /dev/null +++ b/src/Shader.cpp @@ -0,0 +1,114 @@ +#include "Shader.hpp" + +namespace d2 +{ + /*explicit*/ Shader::Shader() + { + m_program = glCreateProgram(); + + load_shader(GL_VERTEX_SHADER, + DUCK_ASSETS / "shaders" / "vertex.glsl"); + + load_shader(GL_FRAGMENT_SHADER, + DUCK_ASSETS / "shaders" / "fragment.glsl"); + + glLinkProgram(m_program); + + GLint status = GL_FALSE; + glGetProgramiv(m_program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) + { + size_t const SZ = 1024; + char msg[SZ]; + glGetProgramInfoLog(m_program, SZ, nullptr, msg); + throw shader_error {"Cannot link program."}; + } + } + + /*virtual*/ Shader::~Shader() + { + glDeleteProgram(m_program); + } + + void Shader::use() const + { + glUseProgram(m_program); + } + + GLint Shader::attr(std::string const& name) const + { + use(); + return glGetAttribLocation(m_program, name.c_str()); + } + + GLint Shader::uniform(std::string const& name) const + { + use(); + return glGetUniformLocation(m_program, name.c_str()); + } + + void Shader::set_matrix(std::string const& name, glm::mat4 matrix) const + { + use(); + glUniformMatrix4fv(uniform(name), 1, false, glm::value_ptr(matrix)); + } + + GLuint Shader::load_shader(GLenum type, + std::filesystem::path source_path) + { + // Load source + // ----------- + if (!std::filesystem::is_regular_file(source_path)) + { + throw shader_error {"Cannot find shader '" + + source_path.string() + "'."}; + } + + std::stringstream ss; + std::ifstream file { source_path }; + + if (!file) + { + throw shader_error {"Cannot open shader '" + + source_path.string() + + "'"}; + } + + ss << file.rdbuf(); + + // Compile source + // -------------- + std::string source = ss.str(); + char const* c_source[] = {source.c_str()}; + + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, c_source, nullptr); + + glCompileShader(shader); + + // check compilation status + // ------------------------ + GLint status = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + + if (status == GL_FALSE) + { + size_t const SZ = 1024; + char msg[SZ]; + glGetShaderInfoLog(shader, SZ, nullptr, msg); + + std::stringstream ss; + ss << "Cannot compile shader '" + << source_path.string() << "'" << std::endl; + + ss << msg << std::endl; + + throw shader_error {ss.str()}; + } + + glAttachShader(m_program, shader); + + return shader; + } +} diff --git a/src/Shader.hpp b/src/Shader.hpp new file mode 100644 index 0000000..77d8f67 --- /dev/null +++ b/src/Shader.hpp @@ -0,0 +1,31 @@ +#ifndef d2_SHADER_HPP +#define d2_SHADER_HPP + +#include +#include +#include +#include "commons.hpp" + +namespace d2 +{ + D2_ERROR(shader_error); + + class Shader + { + public: + explicit Shader(); + virtual ~Shader(); + + void use() const; + GLint attr(std::string const& name) const; + GLint uniform(std::string const& name) const; + void set_matrix(std::string const& name, glm::mat4 matrix) const; + + private: + GLuint m_program; + + GLuint load_shader(GLenum type, std::filesystem::path source_path); + }; +} + +#endif diff --git a/src/Shape.cpp b/src/Shape.cpp new file mode 100644 index 0000000..6d40c16 --- /dev/null +++ b/src/Shape.cpp @@ -0,0 +1,24 @@ +#include "Shape.hpp" + +namespace d2 +{ + /*explicit*/ Shape::Shape() + { + } + + /*virtual*/ Shape::~Shape() + { + } + + void Shape::set_rect() + { + auto const a = glm::vec3 {-0.5f, -0.5f, +0.0f}; + auto const b = glm::vec3 {+0.5f, -0.5f, +0.0f}; + auto const c = glm::vec3 {+0.5f, +0.5f, +0.0f}; + auto const d = glm::vec3 {-0.5f, +0.5f, +0.0f}; + // a -- b + // | | + // d -- c + m_points = {a, b, c, a, c, d}; + } +} diff --git a/src/Shape.hpp b/src/Shape.hpp new file mode 100644 index 0000000..e434c55 --- /dev/null +++ b/src/Shape.hpp @@ -0,0 +1,24 @@ +#ifndef d2_SHAPE_HPP +#define d2_SHAPE_HPP + +#include +#include "commons.hpp" + +namespace d2 +{ + class Shape + { + public: + explicit Shape(); + virtual ~Shape(); + + std::vector const& points() const { return m_points; } + + void set_rect(); + + private: + std::vector m_points; + }; +} + +#endif diff --git a/src/commons.hpp b/src/commons.hpp new file mode 100644 index 0000000..1428d1f --- /dev/null +++ b/src/commons.hpp @@ -0,0 +1,21 @@ +#ifndef d2_COMMONS_HPP +#define d2_COMMONS_HPP + +#include +#include +#include +#include +#include +#include + +#include "conf.hpp" + +#define D2_ERROR(NAME) \ + struct NAME : public std::runtime_error { \ + explicit NAME(std::string const& what): \ + std::runtime_error { what } \ + { \ + } \ + } + +#endif diff --git a/src/conf.in.hpp b/src/conf.in.hpp index 2c4ede9..284cddc 100644 --- a/src/conf.in.hpp +++ b/src/conf.in.hpp @@ -2,5 +2,5 @@ #define d2_CONF_HPP #define DUCK_VERSION std::string("@version@") - +#define DUCK_ASSETS std::filesystem::path("@assets_dir@") #endif diff --git a/src/main.cpp b/src/main.cpp index 4ba7dc4..5dde966 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,20 @@ #include -#include "conf.hpp" +#include "commons.hpp" +#include "Game.hpp" +#include "Script.hpp" -int main(int, char**) +int main(int argc, char** argv) { - std::cout << "Duck2D " << DUCK_VERSION << std::endl; + d2::Script::init(); + + if (argc > 1) + { + d2::Script::run(argv[1]); + } + else if (std::filesystem::exists(std::filesystem::path("main.scm"))) + { + d2::Script::run("main.scm"); + } + return 0; }