#include #include #include "cbt/gfx.hpp" #include "glad/glad.h" #include "glm/gtc/type_ptr.hpp" #include "cbt/opengl/shader.hpp" #include "cbt/opengl/buffer.hpp" #include "cbt/opengl/vao.hpp" #include "cbt/opengl/texture.hpp" namespace cbt::gfx { struct pipeline::impl { opengl::shader m_prog; opengl::buffer m_vbo; opengl::buffer m_ebo{opengl::buffer_type::index}; opengl::vao m_vao; GLint m_loc_proj = -1; GLint m_loc_view = -1; GLint m_loc_model = -1; GLsizei m_index_count = 0; GLsizei m_vertex_count = 0; bool m_valid = false; bool m_uses_index = false; bool m_depth_test = true; GLenum m_draw_mode = GL_TRIANGLES; GLenum m_index_gl_type = GL_UNSIGNED_INT; auto build(pipeline_desc const& desc) -> bool; auto bind_texture(char const* sampler_name, std::uint32_t texture_id, std::uint32_t unit) const -> void; auto bind_vec3(char const* name, glm::vec3 const& v) const -> void; auto bind_float(char const* name, float v) const -> void; }; auto pipeline::impl::build(pipeline_desc const& desc) -> bool { if (!desc.vertex_shader_src || !desc.fragment_shader_src || desc.vertex_data.empty() || desc.attributes.empty() || desc.vertex_stride == 0) { return false; } if (!m_prog.compile_vertex(desc.vertex_shader_src) || !m_prog.compile_fragment(desc.fragment_shader_src) || !m_prog.link()) { return false; } m_loc_proj = glGetUniformLocation(m_prog.id(), "u_proj"); m_loc_view = glGetUniformLocation(m_prog.id(), "u_view"); m_loc_model = glGetUniformLocation(m_prog.id(), "u_model"); m_vbo.upload(desc.vertex_data.data(), desc.vertex_data.size()); m_vertex_count = static_cast(desc.vertex_data.size() / desc.vertex_stride); m_uses_index = !desc.index_data.empty(); if (m_uses_index) { m_ebo.upload(desc.index_data.data(), desc.index_data.size()); std::size_t const idx_size = (desc.index_type_ == index_type::uint16) ? sizeof(std::uint16_t) : sizeof(std::uint32_t); m_index_count = static_cast(desc.index_data.size() / idx_size); m_index_gl_type = (desc.index_type_ == index_type::uint16) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; } m_draw_mode = GL_TRIANGLES; m_depth_test = desc.depth_test; m_vao.bind(); m_vbo.bind(); if (m_uses_index) { m_ebo.bind(); } for (auto const& attr : desc.attributes) { glEnableVertexAttribArray(attr.location); glVertexAttribPointer( attr.location, static_cast(attr.num_components), GL_FLOAT, GL_FALSE, static_cast(desc.vertex_stride), reinterpret_cast(static_cast(attr.offset)) ); } m_vao.unbind(); m_vbo.unbind(); if (m_uses_index) { m_ebo.unbind(); } if (desc.depth_test) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } m_valid = true; return true; } auto pipeline::impl::bind_vec3(char const* name, glm::vec3 const& v) const -> void { if (!m_prog.valid()) return; m_prog.use(); GLint const loc = glGetUniformLocation(m_prog.id(), name); if (loc != -1) { glUniform3fv(loc, 1, glm::value_ptr(v)); } m_prog.unuse(); } auto pipeline::impl::bind_float(char const* name, float v) const -> void { if (!m_prog.valid()) return; m_prog.use(); GLint const loc = glGetUniformLocation(m_prog.id(), name); if (loc != -1) { glUniform1f(loc, v); } m_prog.unuse(); } auto pipeline::impl::bind_texture(char const* sampler_name, std::uint32_t texture_id, std::uint32_t unit) const -> void { if (!m_prog.valid()) { return; } GLint loc = glGetUniformLocation(m_prog.id(), sampler_name); if (loc != -1) { glUniform1i(loc, static_cast(unit)); glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(GL_TEXTURE_2D, texture_id); } } pipeline::pipeline() : m_impl(std::make_unique()) {} pipeline::pipeline(pipeline_desc const& desc) : m_impl(std::make_unique()) { m_impl->build(desc); } pipeline::pipeline(pipeline&& other) noexcept : m_impl(std::move(other.m_impl)) {} auto pipeline::operator=(pipeline&& other) noexcept -> pipeline& { if (this != &other) { m_impl = std::move(other.m_impl); } return *this; } pipeline::~pipeline() = default; auto pipeline::valid() const -> bool { return m_impl && m_impl->m_valid; } auto pipeline::draw( glm::mat4 const& model, glm::mat4 const& view, glm::mat4 const& proj ) const -> void { if (!valid()) { return; } auto& impl = *m_impl; impl.m_prog.use(); if (impl.m_loc_proj != -1) { glUniformMatrix4fv(impl.m_loc_proj, 1, GL_FALSE, glm::value_ptr(proj)); } if (impl.m_loc_view != -1) { glUniformMatrix4fv(impl.m_loc_view, 1, GL_FALSE, glm::value_ptr(view)); } if (impl.m_loc_model != -1) { glUniformMatrix4fv(impl.m_loc_model, 1, GL_FALSE, glm::value_ptr(model)); } if (impl.m_depth_test) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } impl.m_vao.bind(); if (impl.m_uses_index) { glDrawElements(impl.m_draw_mode, impl.m_index_count, impl.m_index_gl_type, nullptr); } else { glDrawArrays(impl.m_draw_mode, 0, impl.m_vertex_count); } impl.m_vao.unbind(); impl.m_prog.unuse(); // Reset texture state to prevent leakage on scene switches or between passes glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); } auto pipeline::bind_texture(char const* sampler_name, std::uint32_t texture_id, std::uint32_t unit) const -> void { if (m_impl) { m_impl->bind_texture(sampler_name, texture_id, unit); } } auto pipeline::bind_vec3(char const* name, glm::vec3 const& v) const -> void { if (m_impl) { m_impl->bind_vec3(name, v); } } auto pipeline::bind_float(char const* name, float v) const -> void { if (m_impl) { m_impl->bind_float(name, v); } } struct render_target::impl { GLuint m_fbo = 0; opengl::texture m_color{opengl::texture_target::_2d}; GLuint m_depth_rbo = 0; int m_width = 0; int m_height = 0; bool m_valid = false; auto build(int width, int height) -> bool; auto cleanup() -> void; }; auto render_target::impl::build(int width, int height) -> bool { m_width = width; m_height = height; glGenFramebuffers(1, &m_fbo); if (m_fbo == 0) { return false; } // Color texture m_color = opengl::texture(opengl::texture_target::_2d); m_color.bind(0); m_color.upload(nullptr, width, height, opengl::texture_format::rgba, opengl::texture_type::ubyte); m_color.set_filter(GL_LINEAR, GL_LINEAR); m_color.set_wrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); m_color.unbind(); // Depth renderbuffer glGenRenderbuffers(1, &m_depth_rbo); glBindRenderbuffer(GL_RENDERBUFFER, m_depth_rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); glBindRenderbuffer(GL_RENDERBUFFER, 0); // Attach to FBO glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_color.id(), 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depth_rbo); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (status != GL_FRAMEBUFFER_COMPLETE) { cleanup(); return false; } m_valid = true; return true; } auto render_target::impl::cleanup() -> void { if (m_fbo != 0) { glDeleteFramebuffers(1, &m_fbo); m_fbo = 0; } if (m_depth_rbo != 0) { glDeleteRenderbuffers(1, &m_depth_rbo); m_depth_rbo = 0; } m_valid = false; } render_target::render_target(int width, int height) : m_impl(std::make_unique()) { m_impl->build(width, height); } render_target::render_target(render_target&& other) noexcept : m_impl(std::move(other.m_impl)) {} auto render_target::operator=(render_target&& other) noexcept -> render_target& { if (this != &other) { m_impl = std::move(other.m_impl); } return *this; } render_target::~render_target() { if (m_impl) { m_impl->cleanup(); } } auto render_target::bind() const -> void { if (!valid()) return; glBindFramebuffer(GL_FRAMEBUFFER, m_impl->m_fbo); glViewport(0, 0, m_impl->m_width, m_impl->m_height); } auto render_target::unbind() const -> void { glBindFramebuffer(GL_FRAMEBUFFER, 0); } auto render_target::color_id() const -> std::uint32_t { return m_impl ? m_impl->m_color.id() : 0; } auto render_target::width() const -> int { return m_impl ? m_impl->m_width : 0; } auto render_target::height() const -> int { return m_impl ? m_impl->m_height : 0; } auto render_target::valid() const -> bool { return m_impl && m_impl->m_valid; } auto render_target::resize(int width, int height) -> void { if (m_impl && m_impl->m_width == width && m_impl->m_height == height) { return; } if (m_impl) { m_impl->cleanup(); } if (!m_impl) { m_impl = std::make_unique(); } m_impl->build(width, height); } } // namespace cbt::gfx