diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ed16ad..3bc3ea3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,8 @@ add_executable(cuber "cuber.cpp" "cbt/opengl/buffer.cpp" "cbt/opengl/texture.cpp" "cbt/opengl/descriptor.cpp" + "cbt/opengl/shader.cpp" + "cbt/opengl/vao.cpp" ) target_include_directories(cuber PRIVATE ".") target_compile_features(cuber PRIVATE cxx_std_23) diff --git a/cbt/opengl/shader.cpp b/cbt/opengl/shader.cpp new file mode 100644 index 0000000..0c31153 --- /dev/null +++ b/cbt/opengl/shader.cpp @@ -0,0 +1,110 @@ +#include "cbt/opengl/shader.hpp" + +#include +#include + +#include "fmt/std.h" + +namespace cbt::opengl { + +shader::shader() {} + +shader::~shader() { + if (m_id) { + glDeleteProgram(m_id); + } +} + +shader::shader(shader&& other) noexcept + : m_id(other.m_id) { + other.m_id = 0; +} + +shader& shader::operator=(shader&& other) noexcept { + if (this != &other) { + if (m_id) { + glDeleteProgram(m_id); + } + m_id = other.m_id; + other.m_id = 0; + } + return *this; +} + +auto shader::compile_vertex(const char* source) -> bool { + auto vert = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vert, 1, &source, nullptr); + glCompileShader(vert); + + GLint success; + glGetShaderiv(vert, GL_COMPILE_STATUS, &success); + if (!success) { + GLchar info[512]; + glGetShaderInfoLog(vert, 512, nullptr, info); + fmt::print("Vertex shader compile error:\n{}\n", info); + glDeleteShader(vert); + return false; + } + + if (m_id) { + glDeleteProgram(m_id); + } + m_id = glCreateProgram(); + glAttachShader(m_id, vert); + glDeleteShader(vert); + return true; +} + +auto shader::compile_fragment(const char* source) -> bool { + auto frag = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(frag, 1, &source, nullptr); + glCompileShader(frag); + + GLint success; + glGetShaderiv(frag, GL_COMPILE_STATUS, &success); + if (!success) { + GLchar info[512]; + glGetShaderInfoLog(frag, 512, nullptr, info); + fmt::print("Fragment shader compile error:\n{}\n", info); + glDeleteShader(frag); + return false; + } + + glAttachShader(m_id, frag); + glDeleteShader(frag); + return true; +} + +auto shader::link() -> bool { + glLinkProgram(m_id); + + GLint success; + glGetProgramiv(m_id, GL_LINK_STATUS, &success); + if (!success) { + GLchar info[512]; + glGetProgramInfoLog(m_id, 512, nullptr, info); + fmt::print("Shader link error:\n{}\n", info); + glDeleteProgram(m_id); + m_id = 0; + return false; + } + return true; +} + +auto shader::use() const -> void { + glUseProgram(m_id); +} + +auto shader::unuse() const -> void { + glUseProgram(0); +} + +auto shader::id() const -> GLuint { + return m_id; +} + +auto shader::valid() const -> bool { + return m_id != 0; +} + +} diff --git a/cbt/opengl/shader.hpp b/cbt/opengl/shader.hpp new file mode 100644 index 0000000..0494296 --- /dev/null +++ b/cbt/opengl/shader.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "glad/glad.h" + +namespace cbt::opengl { + +class shader { + GLuint m_id = 0; + +public: + shader(); + ~shader(); + + shader(const shader&) = delete; + shader& operator=(const shader&) = delete; + + shader(shader&& other) noexcept; + shader& operator=(shader&& other) noexcept; + + auto compile_vertex(const char* source) -> bool; + auto compile_fragment(const char* source) -> bool; + auto link() -> bool; + auto use() const -> void; + auto unuse() const -> void; + auto id() const -> GLuint; + auto valid() const -> bool; +}; + +} diff --git a/cbt/opengl/vao.cpp b/cbt/opengl/vao.cpp new file mode 100644 index 0000000..9be7d13 --- /dev/null +++ b/cbt/opengl/vao.cpp @@ -0,0 +1,47 @@ +#include "cbt/opengl/vao.hpp" + +namespace cbt::opengl { + +vao::vao() { + glGenVertexArrays(1, &m_id); +} + +vao::~vao() { + if (m_id) { + glDeleteVertexArrays(1, &m_id); + } +} + +vao::vao(vao&& other) noexcept + : m_id(other.m_id) { + other.m_id = 0; +} + +vao& vao::operator=(vao&& other) noexcept { + if (this != &other) { + if (m_id) { + glDeleteVertexArrays(1, &m_id); + } + m_id = other.m_id; + other.m_id = 0; + } + return *this; +} + +auto vao::bind() const -> void { + glBindVertexArray(m_id); +} + +auto vao::unbind() const -> void { + glBindVertexArray(0); +} + +auto vao::id() const -> GLuint { + return m_id; +} + +auto vao::valid() const -> bool { + return m_id != 0; +} + +} diff --git a/cbt/opengl/vao.hpp b/cbt/opengl/vao.hpp new file mode 100644 index 0000000..41603e5 --- /dev/null +++ b/cbt/opengl/vao.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "glad/glad.h" + +namespace cbt::opengl { + +class vao { + GLuint m_id = 0; + +public: + vao(); + ~vao(); + + vao(const vao&) = delete; + vao& operator=(const vao&) = delete; + + vao(vao&& other) noexcept; + vao& operator=(vao&& other) noexcept; + + auto bind() const -> void; + auto unbind() const -> void; + auto id() const -> GLuint; + auto valid() const -> bool; +}; + +} diff --git a/cuber.cpp b/cuber.cpp index 5313ab2..8ae2f44 100644 --- a/cuber.cpp +++ b/cuber.cpp @@ -2,20 +2,71 @@ #include "GLFW/glfw3.h" #include "cbt/opengl/context.hpp" +#include "cbt/opengl/shader.hpp" +#include "cbt/opengl/buffer.hpp" +#include "cbt/opengl/vao.hpp" -#include - -#include - +#include "asio.hpp" #include "glad/glad.h" -auto main(int, char const*[]) -> int { +auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { auto ctx = cbt::opengl::context("cuber", 1280, 720); if (!ctx.valid()) { return 1; } + // compile shader + auto prog = cbt::opengl::shader(); + + const char* vert_src = R"glsl( + #version 410 core + layout(location = 0) in vec3 a_pos; + layout(location = 1) in vec3 a_color; + out vec3 v_color; + void main() { + gl_Position = vec4(a_pos, 1.0); + v_color = a_color; + } + )glsl"; + + const char* frag_src = R"glsl( + #version 410 core + in vec3 v_color; + out vec4 frag_color; + void main() { + frag_color = vec4(v_color, 1.0); + } + )glsl"; + + if (!prog.compile_vertex(vert_src) || !prog.compile_fragment(frag_src) || !prog.link()) { + return 1; + } + + // vertex data: triangle with per-vertex colors + // red, green, blue corners + std::array vertices = { + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, + }; + + auto vbo = cbt::opengl::buffer(cbt::opengl::buffer_type::vertex); + vbo.upload(vertices.data(), vertices.size() * sizeof(float)); + + // bind VAO and configure vertex attributes + auto vao = cbt::opengl::vao(); + vao.bind(); + vbo.bind(); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), nullptr); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), + reinterpret_cast(3 * sizeof(float))); + vbo.unbind(); + vao.unbind(); + + // signal handling asio::io_context io; asio::signal_set signals(io, SIGINT, SIGTERM); bool quit = false; @@ -29,6 +80,7 @@ auto main(int, char const*[]) -> int { while (io.poll()) {} }; + // render loop while (!ctx.should_close()) { process_signals(); @@ -39,6 +91,12 @@ auto main(int, char const*[]) -> int { glClearColor(0.6f, 0.8f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); + prog.use(); + vao.bind(); + glDrawArrays(GL_TRIANGLES, 0, 3); + vao.unbind(); + prog.unuse(); + ctx.swap_buffers(); ctx.poll_events(); }