initial commit
All checks were successful
Build (Arch Linux) / build (push) Successful in 3m10s

This commit is contained in:
2025-04-16 01:58:29 +01:00
commit a8d8b9b9ab
116 changed files with 106633 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
#include "graphics/opengl/buffer.hpp"
#include <memory>
using namespace kuiper::gl;
buffer::s_ptr buffer::make(GLenum target) noexcept {
GLuint name = 0;
glGenBuffers(1, &name);
if (!name)
return nullptr;
return std::make_shared<buffer>(name, target);
}
void buffer::destroy() noexcept {
if (m_id)
glDeleteBuffers(1, &m_id);
return;
}
void buffer::bind() const noexcept {
return glBindBuffer(m_target, m_id);
}
void buffer::bind_to_location(std::uint32_t location) const noexcept {
return glBindBufferBase(m_target, location, m_id);
}
void buffer::upload(GLenum usage, std::size_t size, const void* data) noexcept {
m_size = size;
bind();
return glBufferData(m_target, static_cast<GLsizeiptr>(size), data, usage);
}
void buffer::update(std::size_t offset, std::size_t size, const void* data) noexcept {
bind();
return glBufferSubData(m_target, static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), data);
}
void buffer::set_vertex_attrib(std::uint32_t index,
std::size_t size,
GLenum type,
std::size_t stride,
std::size_t offset,
bool normalised) noexcept {
bind();
return glVertexAttribPointer(index,
static_cast<GLint>(size),
type,
normalised,
static_cast<GLsizei>(stride),
reinterpret_cast<const void*>(offset));
}

View File

@@ -0,0 +1,118 @@
#include "graphics/opengl/framebuffer.hpp"
#include "graphics/opengl/texture.hpp"
#include "utils/assert.hpp"
#include <glad/gl.h>
#include <memory>
#include <utility>
using namespace kuiper::gl;
framebuffer::s_ptr framebuffer::make() noexcept {
GLuint name = 0;
glGenFramebuffers(1, &name);
if (!name)
return nullptr;
return std::make_shared<framebuffer>(name);
}
framebuffer::s_ptr framebuffer::make_default(std::uint32_t width, std::uint32_t height) noexcept {
if (!width || !height)
return nullptr;
auto fb = framebuffer::make();
if (!fb)
return nullptr;
// Colour
auto col_tex = texture::make();
col_tex->upload(
GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
col_tex->set_param(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
col_tex->set_param(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Depth & stencil
auto depth_tex = texture::make();
depth_tex->upload(GL_DEPTH24_STENCIL8,
static_cast<GLsizei>(width),
static_cast<GLsizei>(height),
GL_DEPTH_STENCIL,
GL_UNSIGNED_INT_24_8,
nullptr);
depth_tex->set_param(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
depth_tex->set_param(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
fb->attach_colour(std::move(col_tex));
fb->attach_depth(std::move(depth_tex));
fb->bind();
const auto fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
return nullptr;
}
return fb;
}
void framebuffer::destroy() noexcept {
if (m_id) {
glDeleteFramebuffers(1, &m_id);
m_id = 0;
}
return;
}
void framebuffer::bind() const noexcept {
return glBindFramebuffer(GL_FRAMEBUFFER, m_id);
}
void framebuffer::resize(std::uint32_t width, std::uint32_t height) noexcept {
m_width = width;
m_height = height;
for (const auto& col_tex : m_colour_texs) {
if (col_tex == nullptr)
continue;
col_tex->resize(width, height);
}
if (m_depth_tex) {
m_depth_tex->resize(width, height);
}
return;
}
void framebuffer::attach_colour(texture::s_ptr&& tex, std::uint32_t attachment_point) noexcept {
KUIPER_ASSERT(attachment_point < framebuffer::max_colour_attachments);
m_colour_texs[attachment_point] = std::move(tex);
const auto& size = m_colour_texs[attachment_point]->dimensions();
m_width = size.x;
m_height = size.y;
bind();
return glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + attachment_point,
GL_TEXTURE_2D,
m_colour_texs[attachment_point]->id(),
0);
}
void framebuffer::attach_depth(texture::s_ptr&& tex) noexcept {
m_depth_tex = std::move(tex);
const auto& size = m_depth_tex->dimensions();
m_width = size.x;
m_height = size.y;
bind();
return glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_depth_tex->id(), 0);
}

View File

@@ -0,0 +1,149 @@
#include "graphics/opengl/program.hpp"
#include "glm/ext/vector_uint3.hpp"
#include "graphics/opengl/shader.hpp"
#include "logging/engine_logger.hpp"
#include <cstddef>
#include <glm/gtc/type_ptr.hpp>
#include <array>
using namespace kuiper::gl;
program::s_ptr program::make() noexcept {
GLuint name = glCreateProgram();
return std::make_shared<program>(name);
}
program::s_ptr program::from_shaders(std::initializer_list<shader::s_ptr> shaders) {
auto prog = program::make();
for (const auto& s : shaders) {
prog->attach_shader(s);
}
if (!prog->link()) {
std::array<char, 0x80> link_err_buf {};
GLsizei n_chars = 0;
glGetProgramInfoLog(prog->id(), link_err_buf.size() - 1 /* null terminator */, &n_chars, link_err_buf.data());
engine_logger::get().error("Failed to link program {}: {}", prog->id(), link_err_buf.data());
return nullptr;
}
return prog;
}
void program::destroy() noexcept {
if (m_id) {
glDeleteProgram(m_id);
m_id = 0;
}
return;
}
void program::use() const noexcept {
return glUseProgram(m_id);
}
void program::attach_shader(const shader::s_ptr& shader) noexcept {
return glAttachShader(m_id, shader->id());
}
bool program::link() noexcept {
glLinkProgram(m_id);
GLint link_status = GL_FALSE;
glGetProgramiv(m_id, GL_LINK_STATUS, &link_status);
return link_status == GL_TRUE;
}
// Uniforms
GLint program::uniform_location(const char* name) const noexcept {
const auto loc = glGetUniformLocation(m_id, name);
if (loc < 0)
engine_logger::get().error("glGetUniformLocation failed for uniform with name: \"{}\"", name);
return loc;
}
void program::set_uint(const char* uniform, std::uint32_t value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniform1ui(loc, value);
}
void program::set_int(const char* uniform, std::int32_t value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniform1i(loc, value);
}
void program::set_int_array(const char* uniform, const std::int32_t* value_arr, std::size_t len) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniform1iv(loc, static_cast<GLsizei>(len), value_arr);
}
void program::set_float(const char* uniform, float value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniform1f(loc, value);
return;
}
void program::set_vec3(const char* uniform, const glm::vec3& value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniform3f(loc, value.x, value.y, value.z);
return;
}
void program::set_uvec3(const char* uniform, const glm::uvec3& value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniform3ui(loc, value.x, value.y, value.z);
return;
}
void program::set_uvec2(const char* uniform, const glm::uvec2& value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniform2ui(loc, value.x, value.y);
return;
}
void program::set_mat3(const char* uniform, const glm::mat3& value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniformMatrix3fv(loc, 1, GL_FALSE, glm::value_ptr(value));
return;
}
void program::set_mat4(const char* uniform, const glm::mat4& value) noexcept {
const auto loc = uniform_location(uniform);
use();
glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(value));
return;
}

View File

@@ -0,0 +1,94 @@
#include "graphics/opengl/render_context.hpp"
#include "logging/engine_logger.hpp"
#include <glad/gl.h>
#include <GLFW/glfw3.h>
using namespace kuiper;
using namespace kuiper::gl;
void gl::initialise() {
int version = gladLoadGL(glfwGetProcAddress);
if (!version) {
throw std::runtime_error("Failed to load & resolve OpenGL symbols with GLAD");
}
// Debug logging
#if !defined(NDEBUG)
GLint flags = 0;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(gl::debug_callback, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
#endif
auto logger = engine_logger::get();
logger.debug("OpenGL version information:");
logger.debug("\tVendor: {}", (const char*) glGetString(GL_VENDOR));
logger.debug("\tRenderer: {}", (const char*) glGetString(GL_RENDERER));
logger.debug("\tVersion: {}", (const char*) glGetString(GL_VERSION));
logger.debug("\tGLSL version: {}", (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
#if !defined(NDEBUG)
dump_hardware_info();
#endif
return;
}
void gl::dump_hardware_info() {
auto& logger = engine_logger::get();
logger.debug("OpenGL driver information:");
logger.debug("\tGL_MAX_FRAGMENT_UNIFORM_BLOCKS: {}", render_context::get<GLint>(GL_MAX_FRAGMENT_UNIFORM_BLOCKS));
logger.debug("\tGL_MAX_UNIFORM_BLOCK_SIZE: {}", render_context::get<GLint>(GL_MAX_UNIFORM_BLOCK_SIZE));
logger.debug("\tGL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: {}",
render_context::get<GLint>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT));
logger.debug("\tGL_MAX_COLOR_ATTACHMENTS: {}", render_context::get<GLint>(GL_MAX_COLOR_ATTACHMENTS));
logger.debug("\tGL_MAX_DRAW_BUFFERS: {}", render_context::get<GLint>(GL_MAX_DRAW_BUFFERS));
logger.debug("\tGL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: {}",
render_context::get<GLint>(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
logger.debug("\tGL_MAX_TEXTURE_IMAGE_UNITS: {}", render_context::get<GLint>(GL_MAX_TEXTURE_IMAGE_UNITS));
logger.debug("\tGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: {}",
render_context::get<GLint>(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS));
return;
}
#if !defined(NDEBUG)
void gl::debug_callback(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam) noexcept {
auto logger = engine_logger::get();
logger.error("OpenGL error: {}", message);
}
#endif
void render_context::bind_framebuffer(const framebuffer::s_ptr& fb) noexcept {
return fb->bind();
}
void render_context::bind_default_framebuffer() noexcept {
return glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void render_context::set_viewport_size(std::uint32_t width, std::uint32_t height) noexcept {
return glViewport(0, 0, static_cast<GLint>(width), static_cast<GLint>(height));
}
void render_context::enable_feature(GLenum feature) noexcept {
return glEnable(feature);
}
void render_context::disable_feature(GLenum feature) noexcept {
return glDisable(feature);
}

View File

@@ -0,0 +1,91 @@
#include "graphics/opengl/shader.hpp"
#include <filesystem>
#include <fstream>
#include <string_view>
#include <vector>
using namespace kuiper::gl;
shader::s_ptr shader::make(shader_type type) noexcept {
GLenum gl_type = 0;
switch (type) {
case shader_type::vertex:
gl_type = GL_VERTEX_SHADER;
break;
case shader_type::fragment:
gl_type = GL_FRAGMENT_SHADER;
break;
case shader_type::compute:
gl_type = GL_COMPUTE_SHADER;
break;
default:
return nullptr;
}
GLuint name = glCreateShader(gl_type);
if (!name)
return nullptr;
return std::make_shared<shader>(name, gl_type);
}
shader::s_ptr shader::from_path(const std::filesystem::path& shader_path, shader_type type) noexcept {
if (shader_path.empty() || !std::filesystem::is_regular_file(shader_path) || !std::filesystem::exists(shader_path))
return nullptr;
const auto file_size = std::filesystem::file_size(shader_path);
std::vector<char> read_buf(file_size + 1); // +1 for NUL terminator
std::ifstream in_file(shader_path); // TODO: std::ios::ate & tellg() trick
if (!in_file.is_open())
return nullptr;
in_file.read(read_buf.data(), file_size);
std::string_view file_ext(shader_path.extension().c_str());
if (type == shader_type::none) {
if (file_ext.compare(".vs") == 0 || file_ext.compare(".vert") == 0) {
type = shader_type::vertex;
} else if (file_ext.compare(".fs") == 0 || file_ext.compare(".frag") == 0) {
type = shader_type::fragment;
} else if (file_ext.compare(".cs") == 0 || file_ext.compare(".comp") == 0) {
type = shader_type::compute;
}
}
return shader::from_source(read_buf.data(), type);
}
shader::s_ptr shader::from_source(std::string_view source, shader_type type) noexcept {
auto shader = shader::make(type);
shader->attach_source(source);
shader->compile();
return shader;
}
void shader::destroy() noexcept {
if (m_id) {
glDeleteShader(m_id);
m_id = 0;
}
return;
}
void shader::attach_source(std::string_view source) noexcept {
const GLint n_shader_sources = 1;
const char* const s = source.data();
const GLint s_len = static_cast<GLint>(source.size());
return glShaderSource(m_id, n_shader_sources, &s, &s_len);
}
void shader::compile() noexcept {
return glCompileShader(m_id);
}

View File

@@ -0,0 +1,136 @@
#include "graphics/opengl/texture.hpp"
#include <filesystem>
#include <stb_image.h>
#include <fstream>
#include <vector>
using namespace kuiper::gl;
texture::s_ptr texture::make(texture_type type) noexcept {
GLuint name = 0;
glGenTextures(1, &name);
if (!name)
return nullptr;
GLenum gl_target = 0;
switch (type) {
case texture_type::tex_2d:
gl_target = GL_TEXTURE_2D;
break;
case texture_type::none:
return nullptr;
}
return std::make_shared<texture>(name, gl_target);
}
texture::s_ptr texture::make_placeholder() noexcept {
// FIXME: this might be cooked because it's endian-dependent
static constexpr std::array<std::uint32_t, 16> data {0xff00ffff,
0xff00ffff,
0x000000ff,
0x000000ff,
0xff00ffff,
0xff00ffff,
0x000000ff,
0x000000ff,
0x000000ff,
0x000000ff,
0xff00ffff,
0xff00ffff,
0x000000ff,
0x000000ff,
0xff00ffff,
0xff00ffff};
auto tex = texture::make();
tex->upload(GL_RGBA, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
tex->gen_mipmaps();
return tex;
}
texture::s_ptr texture::from_image_path(const std::filesystem::path& image_path, bool flip_y) noexcept {
if (image_path.empty() || !std::filesystem::is_regular_file(image_path) || !std::filesystem::exists(image_path))
return nullptr;
const auto file_size = std::filesystem::file_size(image_path);
std::vector<std::uint8_t> read_buf(file_size);
std::ifstream in_file(image_path, std::ios::binary); // TODO: std::ios::ate & tellg() trick
if (!in_file.is_open())
return nullptr;
in_file.read((char*) read_buf.data(), file_size);
return texture::from_image_data(read_buf.data(), read_buf.size(), flip_y);
}
texture::s_ptr texture::from_image_data(const std::uint8_t* buffer, std::size_t length, bool flip_y) noexcept {
if (buffer == nullptr || length == 0)
return nullptr;
stbi_set_flip_vertically_on_load(flip_y);
int width = 0;
int height = 0;
int channels_in_file = 0;
std::uint8_t* image_data =
stbi_load_from_memory(buffer, static_cast<int>(length), &width, &height, &channels_in_file, 4);
if (!image_data)
return nullptr;
auto tex = texture::make();
tex->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
stbi_image_free(image_data);
return tex;
}
void texture::destroy() noexcept {
if (m_id) {
glDeleteTextures(1, &m_id);
m_id = 0;
}
return;
}
void texture::bind() noexcept {
return glBindTexture(m_target, m_id);
}
void texture::upload(
GLint internal_fmt, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* data) noexcept {
m_width = width;
m_height = height;
m_internal_fmt = internal_fmt;
m_pixel_fmt = format;
m_pixel_type = type;
bind();
return glTexImage2D(m_target, 0, internal_fmt, width, height, 0, format, type, data);
}
void texture::resize(std::uint32_t width, std::uint32_t height) noexcept {
return upload(
m_internal_fmt, static_cast<GLsizei>(width), static_cast<GLsizei>(height), m_pixel_fmt, m_pixel_type, nullptr);
}
void texture::set_param(GLenum name, GLint param) noexcept {
bind();
return glTexParameteri(m_target, name, param);
}
void texture::gen_mipmaps() noexcept {
bind();
return glGenerateMipmap(m_target);
}

View File

@@ -0,0 +1,80 @@
#include "graphics/opengl/texture_array.hpp"
#include "graphics/opengl/texture.hpp"
using namespace kuiper::gl;
texture_array::s_ptr texture_array::make(GLenum target) noexcept {
GLuint name = 0;
glCreateTextures(target, 1, &name);
if (!name)
return nullptr;
return std::make_shared<texture_array>(name, target);
}
void texture_array::destroy() noexcept {
if (m_id) {
glDeleteTextures(1, &m_id);
m_id = 0;
}
return;
}
void texture_array::bind(std::uint32_t unit) const noexcept {
return glBindTextureUnit(unit, m_id);
}
void texture_array::set_storage(GLenum internal_fmt,
std::uint32_t width,
std::uint32_t height,
std::uint32_t depth) noexcept {
constexpr GLsizei mip_levels = 1; // only allocating storage for level 0, call gen_mipmaps()
glTextureStorage3D(m_id,
mip_levels,
internal_fmt,
static_cast<GLsizei>(width),
static_cast<GLsizei>(height),
static_cast<GLsizei>(depth));
m_internal_fmt = internal_fmt;
m_width = width;
m_height = height;
m_depth = depth;
return;
}
void texture_array::upload(std::uint32_t x_offset,
std::uint32_t y_offset,
std::uint32_t z_offset,
std::uint32_t width,
std::uint32_t height,
std::uint32_t depth,
GLenum format,
GLenum type,
const void* data,
std::uint32_t mip_level) noexcept {
return glTextureSubImage3D(m_id,
static_cast<GLint>(mip_level),
static_cast<GLint>(x_offset),
static_cast<GLint>(y_offset),
static_cast<GLint>(z_offset),
static_cast<GLsizei>(width),
static_cast<GLsizei>(height),
static_cast<GLsizei>(depth),
format,
type,
data);
}
void texture_array::gen_mipmaps() noexcept {
return glGenerateTextureMipmap(m_id);
}
void texture_array::set_param(GLenum name, GLint param) noexcept {
return glTextureParameteri(m_id, name, param);
}

View File

@@ -0,0 +1,47 @@
#include "graphics/opengl/vertex_array.hpp"
#include <glad/gl.h>
#include <memory>
using namespace kuiper::gl;
vertex_array::s_ptr vertex_array::make() noexcept {
GLuint name = 0;
glGenVertexArrays(1, &name);
if (!name)
return nullptr;
return std::make_shared<vertex_array>(name);
}
void vertex_array::destroy() noexcept {
if (m_id)
glDeleteVertexArrays(1, &m_id);
return;
}
void vertex_array::bind() const noexcept {
return glBindVertexArray(m_id);
}
void vertex_array::enable_attrib(std::uint32_t index) noexcept {
bind();
return glEnableVertexAttribArray(index);
}
void vertex_array::disable_attrib(std::uint32_t index) noexcept {
bind();
return glDisableVertexAttribArray(index);
}
void vertex_array::set_binding_divisor(std::uint32_t binding, std::uint32_t divisor) noexcept {
bind();
return glVertexBindingDivisor(binding, divisor);
}
void vertex_array::set_attrib_divisor(std::uint32_t attrib, std::uint32_t divisor) noexcept {
bind();
return glVertexAttribDivisor(attrib, divisor);
}