This commit is contained in:
66
src/cli/cli.cpp
Normal file
66
src/cli/cli.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "cli/cli.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <expected>
|
||||
#include <format>
|
||||
#include <print>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
void cli::print_usage() {
|
||||
std::println("Usage: doniv-cli [-P process_name | -p process_id] -L library_path");
|
||||
return;
|
||||
}
|
||||
std::string_view cli::cli_args_parse_err_str(cli_args_parse_err err) {
|
||||
switch (err) {
|
||||
case cli_args_parse_err::invalid_argument:
|
||||
return "Invalid command line arguments"sv;
|
||||
case cli_args_parse_err::not_enough_args:
|
||||
return "Not enough command line parameters"sv;
|
||||
default:
|
||||
return ""sv;
|
||||
}
|
||||
}
|
||||
|
||||
std::expected<cli, cli_args_parse_err> cli::try_construct(int argc, char** argv) {
|
||||
if (argc < 1) {
|
||||
return std::unexpected(cli_args_parse_err::not_enough_args);
|
||||
}
|
||||
|
||||
const std::span<char*> cli_args(argv, argc);
|
||||
int root_path_arg_idx = 0;
|
||||
int prefer_x11_arg_idx = 0;
|
||||
|
||||
for (int i = 0; i < cli_args.size(); ++i) {
|
||||
const auto& arg = cli_args[i];
|
||||
const int next_idx = (i + 1);
|
||||
|
||||
if (std::strcmp(arg, "-root") == 0) {
|
||||
if (next_idx < cli_args.size()) {
|
||||
root_path_arg_idx = next_idx;
|
||||
}
|
||||
} else if (std::strcmp(arg, "-x11") == 0) {
|
||||
if (next_idx <= cli_args.size()) {
|
||||
prefer_x11_arg_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path root_path;
|
||||
bool prefer_x11 = false;
|
||||
|
||||
if (root_path_arg_idx) {
|
||||
const auto root_path_cstr = cli_args[root_path_arg_idx];
|
||||
root_path = root_path_cstr;
|
||||
}
|
||||
|
||||
prefer_x11 = prefer_x11_arg_idx;
|
||||
|
||||
return cli {root_path, prefer_x11};
|
||||
}
|
||||
184
src/core/engine.cpp
Normal file
184
src/core/engine.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
#include "core/engine.hpp"
|
||||
|
||||
#include "components/camera.hpp"
|
||||
#include "components/lights.hpp"
|
||||
#include "components/resources.hpp"
|
||||
#include "core/application.hpp"
|
||||
#include "flecs/addons/cpp/c_types.hpp"
|
||||
#include "graphics/opengl/program.hpp"
|
||||
#include "graphics/opengl/shader.hpp"
|
||||
#include "logging/engine_logger.hpp"
|
||||
#include "maths/transform.hpp"
|
||||
#include "renderer/gl_renderer.hpp"
|
||||
#include "resources/model.hpp"
|
||||
#include "resources/resource_manager.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
|
||||
#include <flecs.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include <algorithm> // std::clamp
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cmath> // std::fmod
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
using namespace kuiper;
|
||||
|
||||
engine::engine(std::unique_ptr<kuiper::application>&& app)
|
||||
: m_application(std::move(app)),
|
||||
m_window(std::make_unique<glfw_window>(m_application->get_app_spec().width,
|
||||
m_application->get_app_spec().height,
|
||||
m_application->get_app_spec().name,
|
||||
nullptr,
|
||||
nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
engine::~engine() {
|
||||
// Should be in reverse order of subsystems initialised in Initialise()
|
||||
|
||||
// NOTE: Should free window ptr here?
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::expected<void, std::error_code> engine::initialise(const cli& cli_args) noexcept {
|
||||
// OpenGL expects { 0.0, 0.0 } to be the bottom-left corner of the image
|
||||
stbi_set_flip_vertically_on_load(true);
|
||||
|
||||
// Engine working directory
|
||||
if (!cli_args.root_path.empty()) {
|
||||
std::filesystem::current_path(cli_args.root_path);
|
||||
}
|
||||
|
||||
// GLFW
|
||||
glfw_window::initialise_glfw(cli_args.prefer_x11);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void engine::start() {
|
||||
auto logger = engine_logger::get();
|
||||
logger.info("Kuiper Engine starting");
|
||||
|
||||
m_window->lock_cursor();
|
||||
m_window->enable_raw_input();
|
||||
m_application->initialise(this);
|
||||
auto renderer = gl::renderer::init();
|
||||
|
||||
// Local player & camera
|
||||
|
||||
auto local_player = m_world.entity("Local player");
|
||||
local_player.set<maths::transform_euler>({});
|
||||
|
||||
auto local_camera = m_world.entity("Local camera");
|
||||
local_camera.set<maths::transform_euler>({});
|
||||
local_camera.set<component::camera_props>({});
|
||||
local_camera.add(flecs::ChildOf, local_player);
|
||||
|
||||
// Per-frame flecs queries
|
||||
|
||||
const auto renderables =
|
||||
world().query_builder<maths::transform_euler, component::model_resource>().cached().build();
|
||||
const auto lights = world().query_builder<maths::transform_euler, component::point_light>().cached().build();
|
||||
|
||||
auto last_time_point = std::chrono::high_resolution_clock::now(); // Initialised with first time point
|
||||
while (!m_window->should_close()) {
|
||||
const auto time_now = std::chrono::high_resolution_clock::now();
|
||||
const std::chrono::duration<float> delta_time = time_now - last_time_point;
|
||||
last_time_point = time_now;
|
||||
|
||||
// Input
|
||||
|
||||
m_window->poll_events();
|
||||
|
||||
// State
|
||||
// TODO: [update world state here]
|
||||
|
||||
m_application->pre_render(delta_time.count());
|
||||
|
||||
// Rendering
|
||||
|
||||
{
|
||||
auto rendering_start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
lights.each([&renderer](flecs::entity e,
|
||||
const maths::transform_euler& transform,
|
||||
const component::point_light& light) {
|
||||
renderer.add_light(transform.position, light);
|
||||
});
|
||||
|
||||
// Render every entity with a transform & model attached
|
||||
renderables.each([this, &renderer](flecs::entity e,
|
||||
const maths::transform_euler& transform,
|
||||
const component::model_resource& resource) {
|
||||
const auto& model = m_resources.get<resource::model>(resource.id);
|
||||
if (!model.has_value())
|
||||
return;
|
||||
|
||||
renderer.draw_model(model.value(), transform);
|
||||
});
|
||||
|
||||
// Draw to framebuffers
|
||||
for (const auto& target : m_render_targets) {
|
||||
renderer.render(target, m_cam_pos, m_cam_props);
|
||||
}
|
||||
|
||||
// Draw to default framebuffer
|
||||
if (m_render_to_default) {
|
||||
renderer.render(m_window->back_buffer(), m_cam_pos, m_cam_props);
|
||||
}
|
||||
|
||||
// Debug metrics
|
||||
|
||||
m_metrics.renderer_metrics = renderer.metrics();
|
||||
|
||||
// Flush the renderer state, hand over the default frame buffer to the application
|
||||
|
||||
renderer.flush();
|
||||
|
||||
std::chrono::duration<float, std::milli> render_dur =
|
||||
std::chrono::high_resolution_clock::now() - rendering_start;
|
||||
m_metrics.rendering_time = render_dur.count();
|
||||
}
|
||||
|
||||
m_window->back_buffer()->bind(); // so the user application renders to the window
|
||||
|
||||
m_application->post_render(delta_time.count());
|
||||
m_window->swap_buffers();
|
||||
|
||||
// End
|
||||
}
|
||||
|
||||
m_application->shutdown();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void engine::add_render_target(const gl::framebuffer::s_ptr& target) noexcept {
|
||||
return m_render_targets.push_back(target);
|
||||
}
|
||||
|
||||
void engine::remove_render_target(const gl::framebuffer::s_ptr& target) noexcept {
|
||||
std::ignore = std::remove(m_render_targets.begin(), m_render_targets.end(), target);
|
||||
return;
|
||||
}
|
||||
|
||||
void engine::disable_default_framebuffer() noexcept {
|
||||
m_render_to_default = false;
|
||||
return;
|
||||
}
|
||||
|
||||
void engine::enable_default_framebuffer() noexcept {
|
||||
m_render_to_default = true;
|
||||
return;
|
||||
}
|
||||
|
||||
void engine::set_render_camera(const glm::vec3& pos, const component::camera_props& cam_props) noexcept {
|
||||
m_cam_pos = pos;
|
||||
m_cam_props = cam_props;
|
||||
return;
|
||||
}
|
||||
64
src/core/entrypoint.cpp
Normal file
64
src/core/entrypoint.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "core/entrypoint.hpp"
|
||||
|
||||
#include "cli/cli.hpp"
|
||||
#include "core/application.hpp"
|
||||
#include "core/engine.hpp"
|
||||
#include "logging/engine_logger.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
// Implemented by the user
|
||||
extern std::unique_ptr<kuiper::application> kuiper::create_application(int argc, char* argv[]);
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return kuiper::entrypoint(argc, argv);
|
||||
}
|
||||
|
||||
int kuiper::entrypoint(int argc, char* argv[]) {
|
||||
// Initialise logging
|
||||
|
||||
auto logger = engine_logger::get();
|
||||
|
||||
// Parse command line arguments
|
||||
|
||||
// NOTE: We assume that command line arguments are UTF-8 encoded at this point
|
||||
const auto maybe_args = cli::try_construct(argc, argv);
|
||||
if (!maybe_args) {
|
||||
logger.error("Invalid command like argument: {}", cli::cli_args_parse_err_str(maybe_args.error()));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const auto& cli_args = maybe_args.value();
|
||||
|
||||
// Create instance of client application
|
||||
|
||||
std::unique_ptr<kuiper::application> app = kuiper::create_application(argc, argv);
|
||||
|
||||
if (app == nullptr) {
|
||||
logger.critical("Application was nullptr");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Initialise engine & begin execution
|
||||
|
||||
try {
|
||||
const auto initialised = engine::initialise(cli_args);
|
||||
if (!initialised) {
|
||||
logger.critical("Failed to initialise engine");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
engine engine(std::move(app));
|
||||
engine.start();
|
||||
} catch (const std::exception& ex) {
|
||||
logger.critical("Unhandled std::exception: {}", ex.what());
|
||||
return EXIT_FAILURE;
|
||||
} catch (...) {
|
||||
logger.critical("Unknown unhandled exception!");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS; // TODO: Return error exit code if the app encounters an error during execution
|
||||
}
|
||||
52
src/core/io.cpp
Normal file
52
src/core/io.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "core/io.hpp"
|
||||
|
||||
#include "errors/exceptions.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#if defined(__linux__)
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
using namespace kuiper;
|
||||
|
||||
std::string io::read_text_file(const std::filesystem::path& path) noexcept {
|
||||
if (path.empty() || !std::filesystem::is_regular_file(path) || !std::filesystem::exists(path)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
auto input_file = std::ifstream(path, std::ios::ate);
|
||||
if (!input_file.is_open()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto file_size = input_file.tellg();
|
||||
input_file.seekg(0);
|
||||
|
||||
// Use std::stringstream here (?)
|
||||
std::string buf(static_cast<std::size_t>(file_size) + 1, '\0');
|
||||
input_file.read(&buf[0], file_size);
|
||||
input_file.close();
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::filesystem::path io::get_home_dir() noexcept {
|
||||
#if defined(__linux__)
|
||||
const auto uid = getuid();
|
||||
const auto pw = getpwuid(uid);
|
||||
return {pw->pw_dir};
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
59
src/graphics/opengl/buffer.cpp
Normal file
59
src/graphics/opengl/buffer.cpp
Normal 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));
|
||||
}
|
||||
118
src/graphics/opengl/framebuffer.cpp
Normal file
118
src/graphics/opengl/framebuffer.cpp
Normal 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);
|
||||
}
|
||||
149
src/graphics/opengl/program.cpp
Normal file
149
src/graphics/opengl/program.cpp
Normal 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;
|
||||
}
|
||||
94
src/graphics/opengl/render_context.cpp
Normal file
94
src/graphics/opengl/render_context.cpp
Normal 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);
|
||||
}
|
||||
91
src/graphics/opengl/shader.cpp
Normal file
91
src/graphics/opengl/shader.cpp
Normal 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);
|
||||
}
|
||||
136
src/graphics/opengl/texture.cpp
Normal file
136
src/graphics/opengl/texture.cpp
Normal 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);
|
||||
}
|
||||
80
src/graphics/opengl/texture_array.cpp
Normal file
80
src/graphics/opengl/texture_array.cpp
Normal 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);
|
||||
}
|
||||
47
src/graphics/opengl/vertex_array.cpp
Normal file
47
src/graphics/opengl/vertex_array.cpp
Normal 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);
|
||||
}
|
||||
304
src/platform/glfw/glfw_window.cpp
Normal file
304
src/platform/glfw/glfw_window.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
#include "platform/glfw/glfw_window.hpp"
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include "graphics/opengl/framebuffer.hpp"
|
||||
#include "graphics/opengl/render_context.hpp"
|
||||
|
||||
#include "logging/engine_logger.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
using namespace kuiper;
|
||||
|
||||
glfw_window::glfw_window(
|
||||
std::uint32_t width, std::uint32_t height, std::u8string_view title, GLFWmonitor* monitor, GLFWwindow* share)
|
||||
: m_window_handle(create_glfw_window(width, height, title, monitor, share)),
|
||||
m_default_framebuffer(std::make_shared<gl::framebuffer>(gl::framebuffer(0))) {
|
||||
if (!m_window_handle) {
|
||||
throw std::runtime_error("glfwCreateWindow failed, returned nullptr");
|
||||
}
|
||||
|
||||
// Window resize callback
|
||||
|
||||
glfwSetFramebufferSizeCallback(m_window_handle, glfw_window::glfw_framebuffer_size_callback);
|
||||
glfwSetWindowContentScaleCallback(m_window_handle, glfw_window::glfw_content_scale_callback);
|
||||
|
||||
// Input callbacks
|
||||
|
||||
glfwSetKeyCallback(m_window_handle, glfw_window::glfw_key_event_callback);
|
||||
glfwSetCursorPosCallback(m_window_handle, glfw_window::glfw_cursor_pos_callback);
|
||||
glfwSetScrollCallback(m_window_handle, glfw_window::glfw_scroll_callback);
|
||||
glfwSetDropCallback(m_window_handle, glfw_window::glfw_dragdrop_callback);
|
||||
|
||||
// Make instance of this GLFWWindow available to static callbacks
|
||||
|
||||
glfwSetWindowUserPointer(m_window_handle, this);
|
||||
|
||||
// Initialise OpenGL context
|
||||
|
||||
glfwMakeContextCurrent(m_window_handle);
|
||||
gl::initialise();
|
||||
|
||||
// Fixes framebuffer/viewport not being sized correctly on scalings other than 1.0 x 1.0
|
||||
resize_back_buffer();
|
||||
|
||||
// Enable required OpenGL features
|
||||
|
||||
gl::render_context::enable_feature(GL_DEPTH_TEST); // depth buffer
|
||||
// gl::render_context::disable_feature(GL_STENCIL_TEST); // stencil buffer
|
||||
gl::render_context::enable_feature(GL_CULL_FACE); // cull backfacing triangles
|
||||
gl::render_context::enable_feature(GL_BLEND); // blending
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
glfw_window::~glfw_window() {
|
||||
if (m_window_handle != nullptr) {
|
||||
glfwDestroyWindow(m_window_handle);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void* glfw_window::get_native_handle() const noexcept {
|
||||
return m_window_handle;
|
||||
}
|
||||
|
||||
void glfw_window::resize_back_buffer() noexcept {
|
||||
const auto framebuffer_size = get_framebuffer_size();
|
||||
const auto width = framebuffer_size.x;
|
||||
const auto height = framebuffer_size.y;
|
||||
m_default_framebuffer->resize(width, height);
|
||||
gl::render_context::set_viewport_size(width, height);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::initialise_glfw(bool prefer_x11) {
|
||||
auto logger = engine_logger::get();
|
||||
|
||||
// Check for potential GLFW version mismatches & incompatibilities
|
||||
|
||||
logger.debug("Compiled against GLFW {}.{}.{}", GLFW_VERSION_MAJOR, GLFW_VERSION_MINOR, GLFW_VERSION_REVISION);
|
||||
logger.debug("GLFW version string: {}", glfwGetVersionString());
|
||||
|
||||
int glfw_ver_major = 0, glfw_ver_minor = 0, glfw_ver_patch = 0;
|
||||
glfwGetVersion(&glfw_ver_major, &glfw_ver_minor, &glfw_ver_patch);
|
||||
|
||||
if (glfw_ver_major > GLFW_VERSION_MAJOR || glfw_ver_minor < GLFW_VERSION_MINOR) {
|
||||
logger.warn("Potentially incompatible GLFW loaded: {}.{}.{} (loaded) vs. {}.{}.{} (compiled-for)",
|
||||
glfw_ver_major,
|
||||
glfw_ver_minor,
|
||||
glfw_ver_patch,
|
||||
GLFW_VERSION_MAJOR,
|
||||
GLFW_VERSION_MINOR,
|
||||
GLFW_VERSION_REVISION);
|
||||
}
|
||||
|
||||
// GLFW initialisation
|
||||
|
||||
// Set a callback for logging GLFW errors
|
||||
|
||||
glfwSetErrorCallback(glfw_window::glfw_error_callback);
|
||||
|
||||
// Initialisation hints
|
||||
|
||||
if (prefer_x11) {
|
||||
logger.info("Hinting to GLFW that X11 should be used");
|
||||
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
|
||||
}
|
||||
|
||||
// Disable libdecor because I don't care about it, GTK, GNOME or anything else.
|
||||
// Also it prints out an annoying error in the console when it fails to load it.
|
||||
glfwInitHint(GLFW_WAYLAND_LIBDECOR, GLFW_WAYLAND_DISABLE_LIBDECOR);
|
||||
|
||||
if (!glfwInit()) {
|
||||
throw std::runtime_error("Failed to initialise GLFW");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GLFWwindow* glfw_window::create_glfw_window(std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::u8string_view title,
|
||||
GLFWmonitor* monitor,
|
||||
GLFWwindow* share) noexcept {
|
||||
KUIPER_ASSERT((width < std::numeric_limits<int>::max()) && (height < std::numeric_limits<int>::max()));
|
||||
push_window_hints();
|
||||
|
||||
return glfwCreateWindow(
|
||||
static_cast<int>(width), static_cast<int>(height), reinterpret_cast<const char*>(title.data()), monitor, share);
|
||||
}
|
||||
|
||||
void glfw_window::push_window_hints() noexcept {
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE);
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool glfw_window::should_close() const noexcept {
|
||||
return glfwWindowShouldClose(m_window_handle);
|
||||
}
|
||||
|
||||
void glfw_window::poll_events() noexcept {
|
||||
const auto old_mouse_pos = m_input_state.get_mouse_pos();
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
const auto delta = m_input_state.get_mouse_pos() - old_mouse_pos;
|
||||
m_input_state.set_mouse_delta(delta);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::swap_buffers() noexcept {
|
||||
return glfwSwapBuffers(m_window_handle);
|
||||
}
|
||||
|
||||
void glfw_window::set_swap_interval(std::uint32_t interval) noexcept {
|
||||
return glfwSwapInterval(static_cast<int>(interval));
|
||||
}
|
||||
|
||||
void glfw_window::bind_context() noexcept {
|
||||
*(volatile unsigned long long*) nullptr = 0x1337beefcafebabe;
|
||||
}
|
||||
|
||||
gl::framebuffer::s_ptr glfw_window::back_buffer() const noexcept {
|
||||
return m_default_framebuffer;
|
||||
}
|
||||
|
||||
bool glfw_window::is_cursor_locked() const noexcept {
|
||||
return m_cursor_locked;
|
||||
}
|
||||
|
||||
void glfw_window::lock_cursor() noexcept {
|
||||
glfwSetInputMode(m_window_handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||
m_cursor_locked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::unlock_cursor() noexcept {
|
||||
glfwSetInputMode(m_window_handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||
m_cursor_locked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::enable_raw_input() noexcept {
|
||||
if (glfwRawMouseMotionSupported())
|
||||
glfwSetInputMode(m_window_handle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::disable_raw_input() noexcept {
|
||||
if (glfwRawMouseMotionSupported())
|
||||
glfwSetInputMode(m_window_handle, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
glm::uvec2 glfw_window::get_size() const noexcept {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
glfwGetWindowSize(m_window_handle, &width, &height);
|
||||
|
||||
// I don't understand why the width and height is a signed integer
|
||||
// I'd like to see a window with negative width & height
|
||||
KUIPER_ASSERT((width > 0) && (height > 0));
|
||||
|
||||
return {static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height)};
|
||||
}
|
||||
|
||||
glm::uvec2 glfw_window::get_framebuffer_size() const noexcept {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
glfwGetFramebufferSize(m_window_handle, &width, &height);
|
||||
|
||||
// Once again: why would the framebuffer ever have negative dimensions?
|
||||
KUIPER_ASSERT((width > 0) && (height > 0));
|
||||
|
||||
return {static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height)};
|
||||
}
|
||||
|
||||
glm::vec2 glfw_window::get_content_scale() const noexcept {
|
||||
float xscale = 1.0f;
|
||||
float yscale = 1.0f;
|
||||
glfwGetWindowContentScale(m_window_handle, &xscale, &yscale);
|
||||
|
||||
return glm::vec2(xscale, yscale);
|
||||
}
|
||||
|
||||
void glfw_window::set_title(std::u8string_view title) noexcept {
|
||||
return glfwSetWindowTitle(m_window_handle, reinterpret_cast<const char*>(title.data()));
|
||||
}
|
||||
|
||||
bool glfw_window::is_minimised() const noexcept {
|
||||
int iconified = glfwGetWindowAttrib(m_window_handle, GLFW_ICONIFIED);
|
||||
|
||||
return (iconified == GLFW_TRUE);
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
|
||||
void glfw_window::glfw_error_callback(int error_code, const char* description) {
|
||||
engine_logger::get().error("GLFW error {}: {}", error_code, description);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::glfw_framebuffer_size_callback(GLFWwindow* window, int width, int height) {
|
||||
KUIPER_ASSERT((width > 0) && (height > 0));
|
||||
engine_logger::get().debug("Framebuffer size changed: {} x {}", width, height);
|
||||
|
||||
glfw_window* window_instance = static_cast<glfw_window*>(glfwGetWindowUserPointer(window));
|
||||
window_instance->m_default_framebuffer->resize(width, height);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::glfw_content_scale_callback(GLFWwindow* window, float xscale, float yscale) {
|
||||
KUIPER_ASSERT(xscale > 0 && yscale > 0);
|
||||
engine_logger::get().debug("Window content scale changed: x: {}, y: {}", xscale, yscale);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::glfw_key_event_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
|
||||
glfw_window* window_instance = static_cast<glfw_window*>(glfwGetWindowUserPointer(window));
|
||||
window_instance->m_input_state.set_key_state(key, action);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::glfw_cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) {
|
||||
glfw_window* window_instance = static_cast<glfw_window*>(glfwGetWindowUserPointer(window));
|
||||
window_instance->m_input_state.set_mouse_pos({xpos, ypos});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
|
||||
return;
|
||||
}
|
||||
|
||||
void glfw_window::glfw_dragdrop_callback(GLFWwindow* window, int path_count, const char* paths[]) {
|
||||
return;
|
||||
}
|
||||
423
src/renderer/gl_renderer.cpp
Normal file
423
src/renderer/gl_renderer.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
#include "renderer/gl_renderer.hpp"
|
||||
|
||||
#include "components/camera.hpp"
|
||||
#include "core/io.hpp"
|
||||
#include "graphics/opengl/buffer.hpp"
|
||||
#include "graphics/opengl/render_context.hpp"
|
||||
#include "graphics/opengl/shader.hpp"
|
||||
#include "graphics/opengl/vertex_array.hpp"
|
||||
#include "maths/aspect_ratio.hpp"
|
||||
#include "resources/primitive.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
#include "utils/colours.hpp"
|
||||
|
||||
// These headers are generated using bin2hpp during build
|
||||
#include "clustering_cs.hpp"
|
||||
#include "default_fs.hpp"
|
||||
#include "default_vs.hpp"
|
||||
#include "light_culling_cs.hpp"
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/geometric.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/mat3x3.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/matrix.hpp>
|
||||
#include <glm/trigonometric.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <numeric>
|
||||
#include <string_view>
|
||||
|
||||
using namespace kuiper::gl;
|
||||
|
||||
glm::mat4 camera_to_view_matrix(const glm::vec3& pos, const kuiper::component::camera_props& props) noexcept;
|
||||
|
||||
renderer renderer::init() {
|
||||
gl_state state {};
|
||||
|
||||
// Configuration
|
||||
|
||||
state.max_texture_units = render_context::get<GLint>(GL_MAX_TEXTURE_IMAGE_UNITS);
|
||||
state.texture_unit_slots.resize(state.max_texture_units);
|
||||
std::iota(state.texture_unit_slots.begin(), state.texture_unit_slots.end(), 0);
|
||||
|
||||
// Buffers
|
||||
|
||||
state.vertex_buffer = buffer::make(GL_ARRAY_BUFFER);
|
||||
state.index_buffer = buffer::make(GL_ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
state.vao = vertex_array::make();
|
||||
state.vao->bind();
|
||||
|
||||
constexpr std::uint32_t pos_idx = 0;
|
||||
constexpr std::uint32_t normal_idx = 1;
|
||||
constexpr std::uint32_t tangent_idx = 2;
|
||||
constexpr std::uint32_t uv_idx = 3;
|
||||
constexpr std::uint32_t draw_id_idx = 4;
|
||||
constexpr std::uint32_t model_mat_idx = 5; // mat4 occupies slot 5, 6, 7 & 8
|
||||
constexpr std::uint32_t normal_mat_idx = 9;
|
||||
|
||||
state.vao->enable_attrib(pos_idx);
|
||||
state.vao->enable_attrib(normal_idx);
|
||||
state.vao->enable_attrib(tangent_idx);
|
||||
state.vao->enable_attrib(uv_idx);
|
||||
state.vao->enable_attrib(draw_id_idx);
|
||||
state.vao->enable_attrib(model_mat_idx + 0);
|
||||
state.vao->enable_attrib(model_mat_idx + 1);
|
||||
state.vao->enable_attrib(model_mat_idx + 2);
|
||||
state.vao->enable_attrib(model_mat_idx + 3);
|
||||
state.vao->enable_attrib(normal_mat_idx + 0);
|
||||
state.vao->enable_attrib(normal_mat_idx + 1);
|
||||
state.vao->enable_attrib(normal_mat_idx + 2);
|
||||
|
||||
state.index_buffer->bind();
|
||||
|
||||
state.vertex_buffer->bind();
|
||||
state.vertex_buffer->set_vertex_attrib(
|
||||
pos_idx, glm::vec3::length(), GL_FLOAT, sizeof(renderer::vertex_t), offsetof(renderer::vertex_t, position));
|
||||
state.vertex_buffer->set_vertex_attrib(normal_idx,
|
||||
glm::vec3::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(renderer::vertex_t),
|
||||
offsetof(renderer::vertex_t, normal),
|
||||
true);
|
||||
state.vertex_buffer->set_vertex_attrib(
|
||||
tangent_idx, glm::vec3::length(), GL_FLOAT, sizeof(renderer::vertex_t), offsetof(renderer::vertex_t, tangent));
|
||||
state.vertex_buffer->set_vertex_attrib(
|
||||
uv_idx, glm::vec2::length(), GL_FLOAT, sizeof(renderer::vertex_t), offsetof(renderer::vertex_t, uv), true);
|
||||
|
||||
state.matrix_buffer = buffer::make(GL_ARRAY_BUFFER);
|
||||
state.matrix_buffer->bind();
|
||||
// mat4 is essentially four vec4s
|
||||
state.matrix_buffer->set_vertex_attrib(model_mat_idx + 0,
|
||||
glm::vec4::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(render_matrices),
|
||||
offsetof(render_matrices, model_matrix) + (0 * sizeof(glm::vec4)));
|
||||
state.matrix_buffer->set_vertex_attrib(model_mat_idx + 1,
|
||||
glm::vec4::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(render_matrices),
|
||||
offsetof(render_matrices, model_matrix) + (1 * sizeof(glm::vec4)));
|
||||
state.matrix_buffer->set_vertex_attrib(model_mat_idx + 2,
|
||||
glm::vec4::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(render_matrices),
|
||||
offsetof(render_matrices, model_matrix) + (2 * sizeof(glm::vec4)));
|
||||
state.matrix_buffer->set_vertex_attrib(model_mat_idx + 3,
|
||||
glm::vec4::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(render_matrices),
|
||||
offsetof(render_matrices, model_matrix) + (3 * sizeof(glm::vec4)));
|
||||
state.matrix_buffer->set_vertex_attrib(normal_mat_idx + 0,
|
||||
glm::vec3::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(render_matrices),
|
||||
offsetof(render_matrices, normal_matrix) + (0 * sizeof(glm::vec3)));
|
||||
state.matrix_buffer->set_vertex_attrib(normal_mat_idx + 1,
|
||||
glm::vec3::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(render_matrices),
|
||||
offsetof(render_matrices, normal_matrix) + (1 * sizeof(glm::vec3)));
|
||||
state.matrix_buffer->set_vertex_attrib(normal_mat_idx + 2,
|
||||
glm::vec3::length(),
|
||||
GL_FLOAT,
|
||||
sizeof(render_matrices),
|
||||
offsetof(render_matrices, normal_matrix) + (2 * sizeof(glm::vec3)));
|
||||
|
||||
state.indirect_buffer = buffer::make(GL_DRAW_INDIRECT_BUFFER);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, state.indirect_buffer->id());
|
||||
glVertexAttribIPointer(draw_id_idx,
|
||||
1,
|
||||
GL_UNSIGNED_INT,
|
||||
sizeof(draw_elements_indirect_cmd),
|
||||
reinterpret_cast<const void*>(offsetof(draw_elements_indirect_cmd, base_instance)));
|
||||
|
||||
state.vao->set_attrib_divisor(pos_idx, 0); // every fragment
|
||||
state.vao->set_attrib_divisor(normal_idx, 0);
|
||||
state.vao->set_attrib_divisor(tangent_idx, 0);
|
||||
state.vao->set_attrib_divisor(uv_idx, 0);
|
||||
state.vao->set_attrib_divisor(draw_id_idx, 1); // every instance
|
||||
state.vao->set_attrib_divisor(model_mat_idx + 0, 1);
|
||||
state.vao->set_attrib_divisor(model_mat_idx + 1, 1);
|
||||
state.vao->set_attrib_divisor(model_mat_idx + 2, 1);
|
||||
state.vao->set_attrib_divisor(model_mat_idx + 3, 1);
|
||||
state.vao->set_attrib_divisor(normal_mat_idx + 0, 1);
|
||||
state.vao->set_attrib_divisor(normal_mat_idx + 1, 1);
|
||||
state.vao->set_attrib_divisor(normal_mat_idx + 2, 1);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
state.cluster_buffer = buffer::make(GL_SHADER_STORAGE_BUFFER);
|
||||
state.cluster_buffer->upload(GL_STATIC_COPY, NUM_CLUSTERS * sizeof(cluster), nullptr);
|
||||
|
||||
state.lights_buffer = buffer::make(GL_SHADER_STORAGE_BUFFER);
|
||||
|
||||
// Shaders
|
||||
|
||||
auto vertex = shader::from_source(bin::default_vs, shader_type::vertex);
|
||||
|
||||
// We pre-process the fragment shader to set the correct number of texture unit slots on the system
|
||||
std::string fragment_src(bin::default_fs);
|
||||
constexpr std::string_view fs_search_and_replace {"const uint MAX_FRAG_TEX_UNITS = 32u;"};
|
||||
fragment_src.replace(fragment_src.find(fs_search_and_replace),
|
||||
fs_search_and_replace.length(),
|
||||
std::format("const uint MAX_FRAG_TEX_UNITS = {}u;", state.max_texture_units));
|
||||
auto fragment = shader::from_source(fragment_src, shader_type::fragment);
|
||||
|
||||
state.pbr_shader = program::from_shaders({vertex, fragment});
|
||||
vertex->destroy();
|
||||
fragment->destroy();
|
||||
|
||||
auto clustering_shader = shader::from_source(bin::clustering_cs, shader_type::compute);
|
||||
state.clustering_shader = program::from_shaders({clustering_shader});
|
||||
clustering_shader->destroy();
|
||||
|
||||
auto light_culling_shader = shader::from_source(bin::light_culling_cs, shader_type::compute);
|
||||
state.light_culling_shader = program::from_shaders({light_culling_shader});
|
||||
light_culling_shader->destroy();
|
||||
|
||||
return {std::move(state)};
|
||||
}
|
||||
|
||||
void renderer::render(const framebuffer::s_ptr& framebuffer,
|
||||
const glm::vec3& cam_pos,
|
||||
const component::camera_props& cam_props) noexcept {
|
||||
update_and_upload();
|
||||
|
||||
// Framebuffer
|
||||
|
||||
const auto& framebuffer_size = framebuffer->dimensions();
|
||||
float fb_x = static_cast<float>(framebuffer_size.x);
|
||||
float fb_y = static_cast<float>(framebuffer_size.y);
|
||||
|
||||
framebuffer->bind();
|
||||
render_context::set_viewport_size(framebuffer_size.x, framebuffer_size.y);
|
||||
|
||||
glClearColor(colours::black.r, colours::black.g, colours::black.b, colours::black.a);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// View & projection matrices
|
||||
|
||||
const auto view_matrix = camera_to_view_matrix(cam_pos, cam_props);
|
||||
const auto projection_matrix =
|
||||
glm::perspective(cam_props.fov, maths::aspect_ratio<float>(fb_x, fb_y), NEAR_Z_DEPTH, FAR_Z_DEPTH);
|
||||
|
||||
// Compute shaders: clustering & light culling
|
||||
|
||||
constexpr glm::uvec3 grid_size_vec {NUM_CLUSTERS_X, NUM_CLUSTERS_Y, NUM_CLUSTERS_Z};
|
||||
|
||||
m_state.clustering_shader->use();
|
||||
m_state.clustering_shader->set_float("z_near", NEAR_Z_DEPTH);
|
||||
m_state.clustering_shader->set_float("z_far", FAR_Z_DEPTH);
|
||||
m_state.clustering_shader->set_mat4("inverse_proj_mat", glm::inverse(projection_matrix));
|
||||
m_state.clustering_shader->set_uvec3("grid_size", grid_size_vec);
|
||||
m_state.clustering_shader->set_uvec2("screen_dimensions", framebuffer_size);
|
||||
|
||||
m_state.cluster_buffer->bind_to_location(1);
|
||||
glDispatchCompute(NUM_CLUSTERS_X, NUM_CLUSTERS_Y, NUM_CLUSTERS_Z);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
m_state.light_culling_shader->use();
|
||||
m_state.light_culling_shader->set_mat4("view_mat", view_matrix);
|
||||
|
||||
m_state.cluster_buffer->bind_to_location(1);
|
||||
m_state.lights_buffer->bind_to_location(2);
|
||||
|
||||
glDispatchCompute(NUM_CLUSTERS / LOCAL_SIZE, 1, 1);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
// Geometry & shading
|
||||
|
||||
m_state.vao->bind();
|
||||
|
||||
m_state.pbr_shader->use();
|
||||
m_state.pbr_shader->set_mat4("view_mat", view_matrix);
|
||||
m_state.pbr_shader->set_mat4("view_proj_mat", projection_matrix * view_matrix);
|
||||
|
||||
m_state.pbr_shader->set_float("z_near", NEAR_Z_DEPTH);
|
||||
m_state.pbr_shader->set_float("z_far", FAR_Z_DEPTH);
|
||||
m_state.pbr_shader->set_uvec3("grid_size", grid_size_vec);
|
||||
m_state.pbr_shader->set_uvec2("screen_dimensions", framebuffer_size);
|
||||
m_state.pbr_shader->set_vec3("view_pos", cam_pos);
|
||||
|
||||
m_state.pbr_shader->set_int_array(
|
||||
"packed_tex", m_state.texture_unit_slots.data(), m_state.texture_unit_slots.size());
|
||||
|
||||
for (std::uint32_t draw_count = 0; draw_count < m_draw_cmds.size(); draw_count += m_state.max_texture_units) {
|
||||
for (std::uint32_t i = 0; i < m_state.max_texture_units && i + draw_count < m_materials.size(); ++i) {
|
||||
m_materials[i + draw_count].textures->bind(i);
|
||||
}
|
||||
|
||||
glMultiDrawElementsIndirect(
|
||||
GL_TRIANGLES,
|
||||
GL_UNSIGNED_INT,
|
||||
reinterpret_cast<const void*>(draw_count * sizeof(draw_elements_indirect_cmd)),
|
||||
static_cast<GLsizei>(
|
||||
std::min(static_cast<std::uint32_t>(m_draw_cmds.size() - draw_count), m_state.max_texture_units)),
|
||||
sizeof(draw_elements_indirect_cmd));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void renderer::flush() noexcept {
|
||||
m_draw_cmds.clear();
|
||||
m_matrices.clear();
|
||||
m_vertices.clear();
|
||||
m_indices.clear();
|
||||
m_materials.clear();
|
||||
m_lights.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
renderer_metrics renderer::metrics() const noexcept {
|
||||
renderer_metrics metrics {};
|
||||
|
||||
metrics.n_vertices = m_vertices.size();
|
||||
metrics.n_indices = m_indices.size();
|
||||
metrics.n_draw_cmds = m_draw_cmds.size();
|
||||
metrics.n_matrices = m_matrices.size();
|
||||
metrics.n_materials = m_materials.size();
|
||||
metrics.n_lights = m_lights.size();
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
void renderer::update_and_upload() noexcept {
|
||||
const auto vertices_size_bytes = m_vertices.size() * sizeof(vertex);
|
||||
const auto indices_size_bytes = m_indices.size() * sizeof(renderer::index_t);
|
||||
const auto lights_size_bytes = m_lights.size() * sizeof(renderer::point_light);
|
||||
const auto indirect_cmds_size_bytes = m_draw_cmds.size() * sizeof(draw_elements_indirect_cmd);
|
||||
const auto matrices_size_bytes = m_matrices.size() * sizeof(render_matrices);
|
||||
|
||||
// NOTE: assuming the use of mutable buffers here
|
||||
|
||||
if (m_vertices.size() > m_state.vertex_buffer->size() / sizeof(vertex)) {
|
||||
m_state.vertex_buffer->upload(GL_STREAM_DRAW, vertices_size_bytes, m_vertices.data());
|
||||
} else {
|
||||
m_state.vertex_buffer->update(0, vertices_size_bytes, m_vertices.data());
|
||||
}
|
||||
|
||||
if (m_indices.size() > m_state.index_buffer->size() / sizeof(renderer::index_t)) {
|
||||
m_state.index_buffer->upload(GL_STREAM_DRAW, indices_size_bytes, m_indices.data());
|
||||
} else {
|
||||
m_state.index_buffer->update(0, indices_size_bytes, m_indices.data());
|
||||
}
|
||||
|
||||
if (m_lights.size() > m_state.lights_buffer->size() / sizeof(renderer::point_light)) {
|
||||
m_state.lights_buffer->upload(GL_STREAM_DRAW, lights_size_bytes, m_lights.data());
|
||||
} else {
|
||||
m_state.lights_buffer->update(0, lights_size_bytes, m_lights.data());
|
||||
}
|
||||
|
||||
if (m_draw_cmds.size() > m_state.indirect_buffer->size() / sizeof(draw_elements_indirect_cmd)) {
|
||||
m_state.indirect_buffer->upload(GL_STREAM_DRAW, indirect_cmds_size_bytes, m_draw_cmds.data());
|
||||
} else {
|
||||
m_state.indirect_buffer->update(0, indirect_cmds_size_bytes, m_draw_cmds.data());
|
||||
}
|
||||
|
||||
if (m_matrices.size() > m_state.matrix_buffer->size() / sizeof(render_matrices)) {
|
||||
m_state.matrix_buffer->upload(GL_STREAM_DRAW, matrices_size_bytes, m_matrices.data());
|
||||
} else {
|
||||
m_state.matrix_buffer->update(0, matrices_size_bytes, m_matrices.data());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void renderer::draw_model(const resource::model& model, const maths::transform_euler& transform) {
|
||||
const auto transform_matrix = transform.to_mat4();
|
||||
|
||||
for (const auto& mesh : model.meshes()) {
|
||||
draw_mesh(mesh, model.materials(), transform_matrix);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void renderer::draw_mesh(const resource::mesh& mesh,
|
||||
const std::vector<resource::material>& materials,
|
||||
const glm::mat4& transform) {
|
||||
// From the glTF 2.0 spec:
|
||||
// "When the scale is zero on all three axes [...] implementations are free
|
||||
// to optimize away rendering of the node's mesh and all of the node's
|
||||
// children's meshes"
|
||||
// TODO: optimise for this ^
|
||||
|
||||
// The `transform` parameter is the parent mesh node's local transformation matrix
|
||||
|
||||
const auto rel_mat = transform * mesh.transform();
|
||||
|
||||
for (const auto& prim : mesh.primitives()) {
|
||||
draw_primitive(prim, materials[prim.material_idx], rel_mat);
|
||||
}
|
||||
|
||||
for (const auto& submesh : mesh.submeshes()) {
|
||||
draw_mesh(submesh, materials, rel_mat);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void renderer::draw_primitive(const resource::primitive& primitive,
|
||||
const resource::material& material,
|
||||
const maths::transform_euler& transform) {
|
||||
return draw_primitive(primitive, material, transform.to_mat4());
|
||||
}
|
||||
|
||||
void renderer::draw_primitive(const resource::primitive& primitive,
|
||||
const resource::material& material,
|
||||
const glm::mat4& transform) {
|
||||
const std::uint32_t n_vertices = primitive.vertices.size();
|
||||
const std::uint32_t n_indices = primitive.indices.size();
|
||||
|
||||
const std::uint32_t vtx_start =
|
||||
static_cast<std::uint32_t>(std::distance(std::begin(m_vertices), std::end(m_vertices)));
|
||||
const std::uint32_t idx_start =
|
||||
static_cast<std::uint32_t>(std::distance(std::begin(m_indices), std::end(m_indices)));
|
||||
|
||||
KUIPER_ASSERT(vtx_start <= (std::numeric_limits<std::uint32_t>::max() - n_vertices));
|
||||
KUIPER_ASSERT(idx_start <= (std::numeric_limits<std::uint32_t>::max() - n_indices));
|
||||
|
||||
draw_elements_indirect_cmd cmd {};
|
||||
cmd.count = n_indices;
|
||||
cmd.instance_count = 1;
|
||||
cmd.first_index = idx_start;
|
||||
cmd.base_vertex = vtx_start;
|
||||
cmd.base_instance = m_draw_cmds.size();
|
||||
|
||||
m_vertices.insert(std::end(m_vertices), std::begin(primitive.vertices), std::end(primitive.vertices));
|
||||
m_indices.insert(std::end(m_indices), std::begin(primitive.indices), std::end(primitive.indices));
|
||||
|
||||
m_matrices.push_back({transform, glm::transpose(glm::inverse(glm::mat3(transform)))});
|
||||
m_draw_cmds.push_back(cmd);
|
||||
m_materials.push_back(material);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void renderer::add_light(const glm::vec3& pos, const component::point_light& light) noexcept {
|
||||
m_lights.emplace_back(glm::make_vec4(pos), glm::make_vec4(light.colour), light.intensity, 50.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
glm::mat4 camera_to_view_matrix(const glm::vec3& pos, const kuiper::component::camera_props& props) noexcept {
|
||||
static constexpr glm::vec3 up_unit_vec {0.0f, 1.0f, 0.0f};
|
||||
|
||||
const glm::vec3 direction = {glm::cos(props.euler.y) * glm::cos(props.euler.x),
|
||||
glm::sin(props.euler.x),
|
||||
glm::sin(props.euler.y) * glm::cos(props.euler.x)};
|
||||
const glm::vec3 cam_front = glm::normalize(direction);
|
||||
|
||||
return glm::lookAt(pos, pos + cam_front, up_unit_vec);
|
||||
}
|
||||
102
src/resources/image.cpp
Normal file
102
src/resources/image.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "resources/image.hpp"
|
||||
|
||||
#include "utils/assert.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_resize2.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
using namespace kuiper::resource;
|
||||
|
||||
image image::make_blank(std::uint32_t width, std::uint32_t height, std::uint32_t n_channels) noexcept {
|
||||
const std::size_t size = width * height * n_channels;
|
||||
const std::vector<std::uint8_t> pixel_data(size, 0u);
|
||||
|
||||
return image {width, height, n_channels, pixel_data.data(), pixel_data.size()};
|
||||
}
|
||||
|
||||
std::expected<image, image_error> image::from_path(const std::filesystem::path& path,
|
||||
std::uint8_t num_channels) noexcept {
|
||||
if (path.empty() || !std::filesystem::is_regular_file(path) || !std::filesystem::exists(path))
|
||||
return std::unexpected(image_error {"invalid path"});
|
||||
|
||||
auto input_file = std::ifstream(path, std::ios::binary | std::ios::ate);
|
||||
const auto file_size = input_file.tellg();
|
||||
|
||||
std::vector<std::uint8_t> buf(file_size);
|
||||
input_file.read((char*) buf.data(), file_size);
|
||||
input_file.close();
|
||||
|
||||
return image::from_memory(buf.data(), buf.size(), num_channels);
|
||||
}
|
||||
|
||||
std::expected<image, image_error> image::from_memory(const std::uint8_t* data,
|
||||
std::size_t len,
|
||||
std::uint8_t num_channels) noexcept {
|
||||
stbi_set_flip_vertically_on_load(false);
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int n_channels = 0;
|
||||
|
||||
auto decoded_image_data =
|
||||
stbi_load_from_memory(data, static_cast<int>(len), &width, &height, &n_channels, num_channels);
|
||||
|
||||
if (!decoded_image_data)
|
||||
return std::unexpected(image_error {stbi_failure_reason()});
|
||||
|
||||
image ret {static_cast<std::uint32_t>(width),
|
||||
static_cast<std::uint32_t>(height),
|
||||
static_cast<std::uint32_t>(n_channels),
|
||||
decoded_image_data,
|
||||
static_cast<std::uint32_t>(width * height * num_channels)};
|
||||
|
||||
stbi_image_free(decoded_image_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void image::resize(std::uint32_t width, std::uint32_t height) noexcept {
|
||||
std::vector<std::uint8_t> out_buf(width * height * m_channels);
|
||||
|
||||
stbir_pixel_layout pixel_layout {STBIR_RGBA};
|
||||
|
||||
switch (m_channels) {
|
||||
case 4:
|
||||
pixel_layout = stbir_pixel_layout::STBIR_RGBA;
|
||||
break;
|
||||
case 3:
|
||||
pixel_layout = stbir_pixel_layout::STBIR_RGB;
|
||||
break;
|
||||
case 2:
|
||||
pixel_layout = stbir_pixel_layout::STBIR_2CHANNEL;
|
||||
break;
|
||||
case 1:
|
||||
pixel_layout = stbir_pixel_layout::STBIR_1CHANNEL;
|
||||
break;
|
||||
default:
|
||||
pixel_layout = stbir_pixel_layout::STBIR_RGBA;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!stbir_resize_uint8_srgb(m_data.data(),
|
||||
static_cast<int>(m_width),
|
||||
static_cast<int>(m_height),
|
||||
0, // packed in memory
|
||||
out_buf.data(),
|
||||
static_cast<int>(width),
|
||||
static_cast<int>(height),
|
||||
0, // packed in memory
|
||||
pixel_layout))
|
||||
KUIPER_ASSERT(false);
|
||||
|
||||
m_data = out_buf;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
return;
|
||||
}
|
||||
390
src/resources/model.cpp
Normal file
390
src/resources/model.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
#include "resources/model.hpp"
|
||||
|
||||
#include "errors/errors.hpp"
|
||||
#include "graphics/opengl/texture.hpp"
|
||||
#include "graphics/opengl/texture_array.hpp"
|
||||
#include "logging/engine_logger.hpp"
|
||||
#include "maths/transform.hpp"
|
||||
#include "resources/image.hpp"
|
||||
#include "resources/material.hpp"
|
||||
#include "resources/mesh.hpp"
|
||||
#include "resources/primitive.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
|
||||
#include "fastgltf/core.hpp"
|
||||
#include "fastgltf/glm_element_traits.hpp"
|
||||
#include "fastgltf/tools.hpp"
|
||||
#include "fastgltf/types.hpp"
|
||||
|
||||
#include <glm/geometric.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <format>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
using namespace kuiper;
|
||||
|
||||
resource::mesh parse_mesh_from_root_node(const fastgltf::Asset& asset, const fastgltf::Node& node);
|
||||
std::vector<resource::material> parse_materials(const fastgltf::Asset& asset);
|
||||
|
||||
std::expected<resource::model, kuiper::error> resource::model::from_gltf_path(const std::filesystem::path& gltf_path) {
|
||||
fastgltf::Parser parser {};
|
||||
return model::from_gltf_path(gltf_path, parser);
|
||||
}
|
||||
|
||||
std::expected<resource::model, kuiper::error> resource::model::from_gltf_path(const std::filesystem::path& gltf_path,
|
||||
fastgltf::Parser& gltf_parser) {
|
||||
if (gltf_path.empty() || !std::filesystem::is_regular_file(gltf_path) || !std::filesystem::exists(gltf_path))
|
||||
return {};
|
||||
|
||||
auto logger = engine_logger::get();
|
||||
|
||||
auto data = fastgltf::GltfDataBuffer::FromPath(gltf_path);
|
||||
if (data.error() != fastgltf::Error::None) {
|
||||
logger.error("Failed to load glTF asset from: {}", gltf_path.filename().c_str());
|
||||
return std::unexpected(error::failed_to_load);
|
||||
}
|
||||
|
||||
const fastgltf::Options fastgltf_opts = fastgltf::Options::None | fastgltf::Options::LoadExternalBuffers |
|
||||
fastgltf::Options::LoadExternalImages |
|
||||
fastgltf::Options::GenerateMeshIndices;
|
||||
|
||||
auto asset = gltf_parser.loadGltf(data.get(), gltf_path.parent_path(), fastgltf_opts);
|
||||
if (auto error = asset.error(); error != fastgltf::Error::None) {
|
||||
logger.error("Failed to load glTF asset from: {}", gltf_path.filename().c_str());
|
||||
return std::unexpected(error::failed_to_load);
|
||||
}
|
||||
|
||||
// Parse the glTF scene description
|
||||
|
||||
if (asset->scenes.size() == 0) {
|
||||
logger.error("glTF asset \"{}\" has no scenes", gltf_path.filename().c_str());
|
||||
logger.error("DEBUG: asset created with \"{}\"",
|
||||
asset->assetInfo.has_value() ? asset->assetInfo->generator : "[unknown]");
|
||||
|
||||
return std::unexpected(error::resource_invalid);
|
||||
} else if (asset->scenes.size() > 1) {
|
||||
logger.warn("glTF asset \"{}\" has multiple scenes. Defaulting to scene 0 ...", gltf_path.filename().c_str());
|
||||
logger.warn("DEBUG: asset created with \"{}\"",
|
||||
asset->assetInfo.has_value() ? asset->assetInfo->generator : "[unknown]");
|
||||
}
|
||||
|
||||
class model out_model {};
|
||||
|
||||
// Collect all materials present in the model
|
||||
|
||||
const auto materials = parse_materials(asset.get());
|
||||
|
||||
// Iterate through the default scene node heirarchy
|
||||
|
||||
const std::size_t scene_idx = asset->defaultScene.has_value() ? asset->defaultScene.value() : 0;
|
||||
const auto& scene = asset->scenes[scene_idx];
|
||||
|
||||
for (const std::size_t node_idx : scene.nodeIndices) {
|
||||
out_model.push_mesh(parse_mesh_from_root_node(asset.get(), asset->nodes[node_idx]));
|
||||
}
|
||||
|
||||
for (const auto& mat : materials) {
|
||||
out_model.push_material(mat);
|
||||
}
|
||||
|
||||
return out_model;
|
||||
}
|
||||
|
||||
resource::mesh parse_mesh_from_root_node(const fastgltf::Asset& asset, const fastgltf::Node& node) {
|
||||
// Node (mesh) transform
|
||||
// The glTF specification notes that node transforms are relative to their parent node's transform
|
||||
|
||||
glm::mat4 out_matrix {};
|
||||
|
||||
if (std::holds_alternative<fastgltf::math::fmat4x4>(node.transform)) {
|
||||
// Node contains a transformation matrix
|
||||
|
||||
const auto& node_trs_matrix = std::get<fastgltf::math::fmat4x4>(node.transform);
|
||||
out_matrix = glm::make_mat4x4(node_trs_matrix.data());
|
||||
|
||||
} else if (std::holds_alternative<fastgltf::TRS>(node.transform)) {
|
||||
// Node contains a TRS transform, convert to matrix
|
||||
|
||||
const auto& node_trs = std::get<fastgltf::TRS>(node.transform);
|
||||
|
||||
glm::vec3 pos {node_trs.translation.x(), node_trs.translation.y(), node_trs.translation.z()};
|
||||
glm::quat rot {node_trs.rotation.w(), node_trs.rotation.x(), node_trs.rotation.y(), node_trs.rotation.z()};
|
||||
glm::vec3 scale {node_trs.scale.x(), node_trs.scale.y(), node_trs.scale.z()};
|
||||
|
||||
static constexpr glm::mat4 identity_mat = glm::mat4(1.0f);
|
||||
const glm::mat4 translation = glm::translate(identity_mat, pos);
|
||||
const glm::mat4 rotation = glm::mat4_cast(rot);
|
||||
const glm::mat4 scaling = glm::scale(identity_mat, scale);
|
||||
|
||||
out_matrix = translation * rotation * scaling;
|
||||
}
|
||||
|
||||
// Construct the output node (mesh)
|
||||
|
||||
resource::mesh ret_val {out_matrix};
|
||||
|
||||
// Populate mesh with primitives
|
||||
|
||||
if (node.meshIndex.has_value()) {
|
||||
const auto mesh_idx = node.meshIndex.value();
|
||||
const fastgltf::Mesh& m = asset.meshes[mesh_idx];
|
||||
|
||||
// TODO: Split primitive parsing into separate function
|
||||
|
||||
for (const fastgltf::Primitive& p : m.primitives) {
|
||||
KUIPER_ASSERT(p.type == fastgltf::PrimitiveType::Triangles);
|
||||
|
||||
resource::primitive out_prim {};
|
||||
out_prim.type = primitive_type::triangles; // asserted above
|
||||
|
||||
// Vertex position
|
||||
|
||||
auto pos_attr = p.findAttribute("POSITION");
|
||||
const auto& pos_accessor = asset.accessors[pos_attr->accessorIndex];
|
||||
|
||||
out_prim.vertices.resize(pos_accessor.count);
|
||||
|
||||
fastgltf::iterateAccessorWithIndex<glm::vec3>(
|
||||
asset, pos_accessor, [&out_prim](const glm::vec3& pos, std::size_t idx) {
|
||||
out_prim.vertices[idx].position = pos;
|
||||
});
|
||||
|
||||
// Vertex normals
|
||||
|
||||
auto norm_attr = p.findAttribute("NORMAL");
|
||||
const auto& norm_accessor = asset.accessors[norm_attr->accessorIndex];
|
||||
|
||||
if (norm_accessor.normalized) {
|
||||
fastgltf::iterateAccessorWithIndex<glm::vec3>(
|
||||
asset, norm_accessor, [&out_prim](const glm::vec3& norm, std::size_t idx) {
|
||||
out_prim.vertices[idx].normal = norm;
|
||||
});
|
||||
} else {
|
||||
fastgltf::iterateAccessorWithIndex<glm::vec3>(
|
||||
asset, norm_accessor, [&out_prim](const glm::vec3& norm, std::size_t idx) {
|
||||
out_prim.vertices[idx].normal = glm::normalize(norm);
|
||||
});
|
||||
}
|
||||
|
||||
// Vertex tangents
|
||||
|
||||
auto tan_attr = p.findAttribute("TANGENT");
|
||||
const auto& tan_accessor = asset.accessors[tan_attr->accessorIndex];
|
||||
|
||||
fastgltf::iterateAccessorWithIndex<glm::vec4>(
|
||||
asset, tan_accessor, [&out_prim](const glm::vec4& tan, std::size_t idx) {
|
||||
out_prim.vertices[idx].tangent = tan;
|
||||
});
|
||||
|
||||
// Indices
|
||||
|
||||
const auto& idx_accessor = asset.accessors[p.indicesAccessor.value()];
|
||||
out_prim.indices.resize(idx_accessor.count);
|
||||
fastgltf::copyFromAccessor<std::uint32_t>(asset, idx_accessor, out_prim.indices.data());
|
||||
|
||||
// Materials
|
||||
|
||||
out_prim.material_idx = p.materialIndex.value();
|
||||
|
||||
// Texture coordinates
|
||||
|
||||
const std::size_t tex_uv_idx =
|
||||
asset.materials[p.materialIndex.value()].pbrData.baseColorTexture->texCoordIndex;
|
||||
auto tex_uv_attr = p.findAttribute(std::format("TEXCOORD_{}", tex_uv_idx));
|
||||
const auto& tex_uv_accessor = asset.accessors[tex_uv_attr->accessorIndex];
|
||||
|
||||
fastgltf::iterateAccessorWithIndex<glm::vec2>(
|
||||
asset, tex_uv_accessor, [&out_prim](const glm::vec2& uv, std::size_t idx) {
|
||||
out_prim.vertices[idx].uv = uv;
|
||||
});
|
||||
|
||||
// Add parsed primitive to mesh
|
||||
|
||||
ret_val.add_primitive(out_prim);
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.children.empty()) {
|
||||
for (const auto child_idx : node.children) {
|
||||
const auto submesh = parse_mesh_from_root_node(asset, asset.nodes[child_idx]);
|
||||
ret_val.add_submesh(submesh);
|
||||
}
|
||||
}
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
std::expected<resource::image, resource::image_error> load_material_image(const fastgltf::Asset& asset,
|
||||
const fastgltf::TextureInfo& tex_info) {
|
||||
const std::size_t tex_idx = tex_info.textureIndex;
|
||||
const std::size_t img_idx = asset.textures[tex_idx].imageIndex.value();
|
||||
const fastgltf::Image& img = asset.images[img_idx];
|
||||
|
||||
if (std::holds_alternative<fastgltf::sources::URI>(img.data)) {
|
||||
const auto& path = std::get<fastgltf::sources::URI>(img.data);
|
||||
|
||||
return resource::image::from_path(path.uri.path());
|
||||
|
||||
} else if (std::holds_alternative<fastgltf::sources::Array>(img.data)) {
|
||||
const auto& arr = std::get<fastgltf::sources::Array>(img.data);
|
||||
|
||||
return resource::image::from_memory((const std::uint8_t*) arr.bytes.data(), arr.bytes.size_bytes());
|
||||
|
||||
} else if (std::holds_alternative<fastgltf::sources::BufferView>(img.data)) {
|
||||
const auto& view = std::get<fastgltf::sources::BufferView>(img.data);
|
||||
|
||||
const auto& buffer_view = asset.bufferViews[view.bufferViewIndex];
|
||||
const auto& buffer = asset.buffers[buffer_view.bufferIndex];
|
||||
|
||||
if (std::holds_alternative<fastgltf::sources::Array>(buffer.data)) {
|
||||
const auto& arr = std::get<fastgltf::sources::Array>(buffer.data);
|
||||
|
||||
return resource::image::from_memory((const std::uint8_t*) arr.bytes.data() + buffer_view.byteOffset,
|
||||
arr.bytes.size_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
return std::unexpected(resource::image_error {"unknown error"});
|
||||
}
|
||||
|
||||
std::vector<resource::material> parse_materials(const fastgltf::Asset& asset) {
|
||||
auto logger = engine_logger::get();
|
||||
|
||||
std::vector<resource::material> ret_vec(asset.materials.size());
|
||||
|
||||
for (std::uint32_t i = 0; i < asset.materials.size(); ++i) {
|
||||
const auto& gltf_mat = asset.materials[i];
|
||||
auto& ret_mat = ret_vec[i];
|
||||
|
||||
ret_mat.base_colour_factor = glm::make_vec4(gltf_mat.pbrData.baseColorFactor.data());
|
||||
ret_mat.metallic_factor = gltf_mat.pbrData.metallicFactor;
|
||||
ret_mat.roughness_factor = gltf_mat.pbrData.roughnessFactor;
|
||||
|
||||
// Texture parameters
|
||||
|
||||
GLint min_filter = GL_LINEAR_MIPMAP_NEAREST;
|
||||
GLint mag_filter = GL_LINEAR;
|
||||
GLint wrap_s = GL_REPEAT;
|
||||
GLint wrap_t = GL_REPEAT;
|
||||
|
||||
// Load each of the texture images we're interested in
|
||||
|
||||
// 0: Albedo, 1: Normal, 2: Metallic-Roughness
|
||||
std::array<resource::image, 3> imgs {};
|
||||
|
||||
// Base colour
|
||||
|
||||
if (gltf_mat.pbrData.baseColorTexture.has_value()) {
|
||||
{
|
||||
const auto& tex = asset.textures[gltf_mat.pbrData.baseColorTexture->textureIndex];
|
||||
|
||||
if (tex.samplerIndex.has_value()) {
|
||||
const fastgltf::Sampler& sampler = asset.samplers[tex.samplerIndex.value()];
|
||||
|
||||
if (sampler.minFilter.has_value())
|
||||
min_filter = std::to_underlying(sampler.minFilter.value());
|
||||
if (sampler.magFilter.has_value())
|
||||
mag_filter = std::to_underlying(sampler.magFilter.value());
|
||||
|
||||
wrap_s = std::to_underlying(sampler.wrapS);
|
||||
wrap_t = std::to_underlying(sampler.wrapT);
|
||||
}
|
||||
}
|
||||
|
||||
auto base_colour_img = load_material_image(asset, gltf_mat.pbrData.baseColorTexture.value());
|
||||
|
||||
if (base_colour_img.has_value()) {
|
||||
imgs[0] = std::move(base_colour_img.value());
|
||||
} else {
|
||||
logger.warn("Failed to load material's base colour texture image: {}", base_colour_img.error().message);
|
||||
imgs[0] = resource::image::make_blank(64, 64, 4);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.warn("Material does not have a base colour texture");
|
||||
imgs[0] = resource::image::make_blank(64, 64, 4);
|
||||
}
|
||||
|
||||
// Normal map
|
||||
|
||||
if (gltf_mat.normalTexture.has_value()) {
|
||||
auto normal_img = load_material_image(asset, gltf_mat.normalTexture.value());
|
||||
|
||||
if (normal_img.has_value()) {
|
||||
imgs[1] = std::move(normal_img.value());
|
||||
} else {
|
||||
logger.warn("Failed to load material's normal texture image: {}", normal_img.error().message);
|
||||
imgs[1] = resource::image::make_blank(64, 64, 4);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.warn("Material does not have a normal map texture");
|
||||
imgs[1] = resource::image::make_blank(64, 64, 4);
|
||||
}
|
||||
|
||||
if (gltf_mat.pbrData.metallicRoughnessTexture.has_value()) {
|
||||
auto met_rough_img = load_material_image(asset, gltf_mat.pbrData.metallicRoughnessTexture.value());
|
||||
|
||||
if (met_rough_img.has_value()) {
|
||||
imgs[2] = std::move(met_rough_img.value());
|
||||
} else {
|
||||
logger.warn("Failed to load material's metallic-roughness texture image: {}",
|
||||
met_rough_img.error().message);
|
||||
imgs[2] = resource::image::make_blank(64, 64, 4);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.warn("Material does not have a metallic-roughness texture");
|
||||
imgs[2] = resource::image::make_blank(64, 64, 4);
|
||||
}
|
||||
|
||||
// Texture 2D arrays require that each layer has the same width & height
|
||||
// This is used to scale all textures in this material to the same size later on
|
||||
|
||||
std::uint32_t max_width = 0;
|
||||
std::uint32_t max_height = 0;
|
||||
|
||||
for (const auto& img : imgs) {
|
||||
KUIPER_ASSERT(img.width() == img.height());
|
||||
|
||||
if (img.width() > max_width)
|
||||
max_width = img.width();
|
||||
|
||||
if (img.height() > max_height)
|
||||
max_height = img.height();
|
||||
}
|
||||
|
||||
for (auto& img : imgs) {
|
||||
if (img.width() < max_width || img.height() < img.height())
|
||||
img.resize(max_width, max_height);
|
||||
}
|
||||
|
||||
ret_mat.textures = gl::texture_array::make(GL_TEXTURE_2D_ARRAY);
|
||||
ret_mat.textures->set_storage(GL_RGBA8, max_width, max_height, 3);
|
||||
ret_mat.textures->upload(
|
||||
0, 0, 0, max_width, max_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, imgs[0].pixel_data().data());
|
||||
ret_mat.textures->upload(
|
||||
0, 0, 1, max_width, max_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, imgs[1].pixel_data().data());
|
||||
ret_mat.textures->upload(
|
||||
0, 0, 2, max_width, max_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, imgs[2].pixel_data().data());
|
||||
|
||||
ret_mat.textures->set_param(GL_TEXTURE_MIN_FILTER, min_filter);
|
||||
ret_mat.textures->set_param(GL_TEXTURE_MAG_FILTER, mag_filter);
|
||||
ret_mat.textures->set_param(GL_TEXTURE_WRAP_S, wrap_s);
|
||||
ret_mat.textures->set_param(GL_TEXTURE_WRAP_T, wrap_t);
|
||||
|
||||
ret_mat.textures->gen_mipmaps();
|
||||
}
|
||||
|
||||
return ret_vec;
|
||||
}
|
||||
7
src/resources/resource_index.cpp
Normal file
7
src/resources/resource_index.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "resources/resource_index.hpp"
|
||||
|
||||
using namespace kuiper;
|
||||
|
||||
resource::index resource::index::from_file(const std::filesystem::path& index_path) noexcept {
|
||||
return {};
|
||||
}
|
||||
39
src/resources/resource_manager.cpp
Normal file
39
src/resources/resource_manager.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "resources/resource_manager.hpp"
|
||||
|
||||
#include "resources/model.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
using namespace kuiper;
|
||||
|
||||
static constexpr std::array<std::string_view, 2> GLTF_FILE_EXTENSIONS {".gltf", ".glb"};
|
||||
|
||||
std::optional<resource_metadata> resource_manager::import_from_path(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::exists(path) || !std::filesystem::is_regular_file(path)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string file_ext = path.extension().string();
|
||||
std::transform(file_ext.begin(), file_ext.end(), file_ext.begin(), [](const auto c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
|
||||
const auto model_ext_it =
|
||||
std::find_if(GLTF_FILE_EXTENSIONS.begin(), GLTF_FILE_EXTENSIONS.end(), [&file_ext](const auto s) {
|
||||
return file_ext == s;
|
||||
});
|
||||
|
||||
if (model_ext_it != GLTF_FILE_EXTENSIONS.end()) {
|
||||
auto maybe_model = resource::model::from_gltf_path(path);
|
||||
if (maybe_model)
|
||||
add<resource::model>(std::move(maybe_model.value()), path.relative_path().string());
|
||||
}
|
||||
|
||||
return std::optional<resource_metadata>();
|
||||
}
|
||||
64
src/window/input_system.cpp
Normal file
64
src/window/input_system.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "GLFW/glfw3.h"
|
||||
|
||||
#include "window/input_system.hpp"
|
||||
|
||||
using namespace kuiper;
|
||||
|
||||
void input_system::set_key_state(int key, int state) {
|
||||
m_key_states[key].state = state;
|
||||
|
||||
// Call any action handlers
|
||||
|
||||
if (state == GLFW_PRESS) {
|
||||
for (const auto& f : m_action_listeners[m_action_keymap[key]]) {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int input_system::get_key_state(int key) const {
|
||||
return m_key_states[key].state;
|
||||
}
|
||||
|
||||
bool input_system::is_key_pressed(int key) const {
|
||||
return m_key_states[key].state == GLFW_PRESS;
|
||||
}
|
||||
|
||||
bool input_system::is_key_released(int key) const {
|
||||
return m_key_states[key].state == GLFW_RELEASE;
|
||||
}
|
||||
|
||||
bool input_system::is_key_held(int key) const {
|
||||
return m_key_states[key].state == GLFW_REPEAT;
|
||||
}
|
||||
|
||||
bool input_system::is_key_down(int key) const {
|
||||
return m_key_states[key].state == GLFW_PRESS || m_key_states[key].state == GLFW_REPEAT;
|
||||
}
|
||||
|
||||
// Mouse
|
||||
|
||||
void input_system::set_mouse_pos(const glm::vec2& pos) {
|
||||
m_mouse_state.delta_pos = m_old_mouse_pos - pos;
|
||||
m_mouse_state.pos = pos;
|
||||
return;
|
||||
}
|
||||
|
||||
void input_system::set_mouse_delta(const glm::vec2& pos) {
|
||||
m_mouse_state.delta_pos = pos;
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec2 input_system::get_mouse_pos() const {
|
||||
return m_mouse_state.pos;
|
||||
}
|
||||
|
||||
glm::vec2 input_system::get_mouse_delta() const {
|
||||
return m_mouse_state.delta_pos;
|
||||
}
|
||||
|
||||
void input_system::subscribe_to_action(action_type type, const action_fn_t& callback) {
|
||||
m_action_listeners[type].push_back(callback);
|
||||
}
|
||||
Reference in New Issue
Block a user