This commit is contained in:
23
include/cli/cli.hpp
Normal file
23
include/cli/cli.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
|
||||
enum class cli_args_parse_err : std::uint8_t {
|
||||
not_enough_args,
|
||||
invalid_argument,
|
||||
};
|
||||
|
||||
class cli {
|
||||
public:
|
||||
static std::expected<cli, cli_args_parse_err> try_construct(int argc, char** argv);
|
||||
|
||||
static void print_usage();
|
||||
static std::string_view cli_args_parse_err_str(cli_args_parse_err err);
|
||||
|
||||
public:
|
||||
std::filesystem::path root_path {};
|
||||
bool prefer_x11 = false;
|
||||
};
|
||||
20
include/components/camera.hpp
Normal file
20
include/components/camera.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/ext/scalar_constants.hpp> // glm::pi
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace component
|
||||
{
|
||||
|
||||
struct camera_props {
|
||||
glm::vec3 euler {0.0f, -(glm::pi<float>() / 2.0f), (glm::pi<float>() / 2.0f)}; // looking down the -Z axis
|
||||
float fov = (glm::pi<float>() / 2.0f); // 90 degrees
|
||||
};
|
||||
|
||||
} // namespace component
|
||||
|
||||
} // namespace kuiper
|
||||
22
include/components/lights.hpp
Normal file
22
include/components/lights.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace component
|
||||
{
|
||||
|
||||
struct directional_light {
|
||||
glm::vec3 direction {0.0f, -1.0f, 0.0f}; // Initial points straight down
|
||||
};
|
||||
|
||||
struct point_light {
|
||||
glm::vec3 colour {1.0f, 1.0f, 1.0f};
|
||||
float intensity {1.0f};
|
||||
};
|
||||
|
||||
} // namespace component
|
||||
|
||||
} // namespace kuiper
|
||||
16
include/components/physics.hpp
Normal file
16
include/components/physics.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace component
|
||||
{
|
||||
|
||||
struct physics_props {
|
||||
float mass {0.0f};
|
||||
float velocity {0.0f};
|
||||
};
|
||||
|
||||
} // namespace component
|
||||
|
||||
} // namespace kuiper
|
||||
17
include/components/resources.hpp
Normal file
17
include/components/resources.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "resources/resource_manager.hpp"
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace component
|
||||
{
|
||||
|
||||
struct model_resource {
|
||||
resource_id_t id {0};
|
||||
};
|
||||
|
||||
} // namespace component
|
||||
|
||||
} // namespace kuiper
|
||||
152
include/containers/sparse_map.hpp
Normal file
152
include/containers/sparse_map.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#pragma once
|
||||
|
||||
#include "containers/sparse_set.hpp"
|
||||
|
||||
#include <cstddef> // std::size_t
|
||||
#include <format> // std::format
|
||||
#include <stdexcept> // std::out_of_range
|
||||
#include <utility> // std::get
|
||||
#include <vector> // values container
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace containers
|
||||
{
|
||||
|
||||
template<typename K, typename V>
|
||||
class sparse_map {
|
||||
public:
|
||||
using sparse_set_t = containers::sparse_set<K>;
|
||||
using values_container_t = std::vector<V>;
|
||||
|
||||
public:
|
||||
// Capacity
|
||||
|
||||
/// Reserve a number of slots in the underlying containers
|
||||
void reserve(std::size_t size) {
|
||||
if (size > m_capacity) {
|
||||
m_values.resize(size);
|
||||
m_capacity = size;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Access
|
||||
|
||||
/// Access value specified by key (non-const)
|
||||
V& at(const K& key) {
|
||||
if (!m_sparse_set.contains(key)) {
|
||||
throw std::out_of_range(std::format("key \"{}\" does not exist in the sparse map", key));
|
||||
}
|
||||
|
||||
const auto dense_idx = m_sparse_set.sparse()[key];
|
||||
return m_values[dense_idx];
|
||||
}
|
||||
|
||||
/// Access value specified by key (const, read-only)
|
||||
const V& at(const K& key) const {
|
||||
if (!m_sparse_set.contains(key)) {
|
||||
throw std::out_of_range(std::format("key \"{}\" does not exist in the sparse map", key));
|
||||
}
|
||||
|
||||
const auto dense_idx = m_sparse_set.sparse()[key];
|
||||
return m_values[dense_idx];
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
|
||||
void insert(const K& key, const V& value) {
|
||||
// 1. Insert key into the underlying sparse set
|
||||
// 2. Insert value into the corresponding index in the sparse map
|
||||
|
||||
if (!m_sparse_set.contains(key)) {
|
||||
// Key does not yet exist in set
|
||||
|
||||
const auto insertion = m_sparse_set.insert(key);
|
||||
|
||||
if (!std::get<1>(insertion))
|
||||
return;
|
||||
|
||||
const auto dense_idx = std::get<0>(insertion);
|
||||
reserve(dense_idx + 1); // no-op if there's already enough space
|
||||
m_values[dense_idx] = value;
|
||||
++m_size;
|
||||
|
||||
return;
|
||||
} else {
|
||||
// Key already exists in set
|
||||
|
||||
const auto dense_idx = m_sparse_set.sparse()[key];
|
||||
m_values[dense_idx] = value;
|
||||
++m_size;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove an element from the map
|
||||
void erase(const K& key) {
|
||||
// 1. Retrieve dense array index from set
|
||||
// 2. Swap to back
|
||||
// 3. Erase from set
|
||||
// 4. Decrement number of elements
|
||||
|
||||
if (contains(key)) {
|
||||
const auto dense_idx = m_sparse_set.sparse()[key];
|
||||
m_values[dense_idx] = m_values[m_size - 1];
|
||||
m_sparse_set.erase(key);
|
||||
--m_size;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup
|
||||
|
||||
/// Check if the key exists in the map
|
||||
bool contains(const K& key) const {
|
||||
return m_sparse_set.contains(key);
|
||||
}
|
||||
|
||||
// Observers
|
||||
|
||||
/// Access to the underlying sparse_set container
|
||||
const sparse_set_t& sparse_set() const noexcept {
|
||||
return m_sparse_set;
|
||||
}
|
||||
|
||||
/// Access to the underlying values container
|
||||
const values_container_t& values() const noexcept {
|
||||
return m_values;
|
||||
}
|
||||
|
||||
// Operator overloads
|
||||
|
||||
/// Access/insert element specified by key
|
||||
V& operator[](const K& key) {
|
||||
const auto k = key;
|
||||
|
||||
if (!contains(k)) {
|
||||
insert(k, {});
|
||||
}
|
||||
|
||||
return at(k);
|
||||
}
|
||||
|
||||
/// Access element specified by key (const, read-only)
|
||||
const V& operator[](const K& key) const {
|
||||
return at(key);
|
||||
}
|
||||
|
||||
private:
|
||||
containers::sparse_set<K> m_sparse_set {};
|
||||
values_container_t m_values {};
|
||||
std::size_t m_size = 0;
|
||||
std::size_t m_capacity = 0;
|
||||
};
|
||||
|
||||
} // namespace containers
|
||||
|
||||
} // namespace kuiper
|
||||
109
include/containers/sparse_set.hpp
Normal file
109
include/containers/sparse_set.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts> // std::unsigned_integral
|
||||
#include <cstddef> // std::size_t
|
||||
#include <iterator> // std::distance
|
||||
#include <utility> // std::pair
|
||||
#include <vector> // Underlying arrays
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace containers
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
requires std::unsigned_integral<T>
|
||||
class sparse_set {
|
||||
public:
|
||||
using sparse_container_t = std::vector<T>;
|
||||
using dense_container_t = std::vector<T>;
|
||||
|
||||
public:
|
||||
// Capacity
|
||||
|
||||
/// Reserve a number of slots in the underlying containers
|
||||
void reserve(std::size_t size) {
|
||||
if (size > m_capacity) {
|
||||
m_sparse.resize(size);
|
||||
m_dense.resize(size);
|
||||
m_capacity = size;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
|
||||
/// Add an element to the set, returns `(dense index, inserted)` pair
|
||||
std::pair<std::size_t, bool> insert(const T& value) {
|
||||
// 1. Append to next slot in the dense array
|
||||
// 2. Record dense array index (current m_size) in sparse array
|
||||
// 3. Increment number of elements
|
||||
|
||||
if (!contains(value)) {
|
||||
reserve(value + 1); // no-op if there's already enough space
|
||||
|
||||
m_dense[m_size] = value;
|
||||
m_sparse[value] = m_size;
|
||||
++m_size;
|
||||
|
||||
return std::make_pair(std::distance(m_dense.cbegin(), m_dense.cbegin() + m_sparse[value]), true);
|
||||
} else {
|
||||
// Value already exists in set, return the index, but not inserted flag
|
||||
return std::make_pair(std::distance(m_dense.cbegin(), m_dense.cbegin() + m_sparse[value]), false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove an element from the set
|
||||
void erase(const T& value) {
|
||||
// 1. Swap element to be removed to the back of the dense array
|
||||
// 2. Update the dense array index for the value which was swapped
|
||||
// 3. Decrement number of elements
|
||||
|
||||
if (contains(value)) {
|
||||
m_dense[m_sparse[value]] = m_dense[m_size - 1];
|
||||
// m_dense[m_size - 1] = m_dense[m_sparse[value]];
|
||||
m_sparse[m_dense[m_size - 1]] = m_sparse[value];
|
||||
--m_size;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// Clear all elements from the set
|
||||
void clear() {
|
||||
m_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup
|
||||
|
||||
/// Check if a value is in the set
|
||||
bool contains(const T& value) const {
|
||||
// First condition will short-circuit if `value` is out-of-range
|
||||
return !(value >= m_capacity) && (m_sparse[value] < m_size) && (m_dense[m_sparse[value]] == value);
|
||||
}
|
||||
|
||||
// Observers
|
||||
|
||||
/// Access to the underlying sparse container
|
||||
const sparse_container_t& sparse() const noexcept {
|
||||
return m_sparse;
|
||||
}
|
||||
|
||||
/// Access to the underlying dense container
|
||||
const dense_container_t& dense() const noexcept {
|
||||
return m_dense;
|
||||
}
|
||||
|
||||
private:
|
||||
sparse_container_t m_sparse {};
|
||||
dense_container_t m_dense {};
|
||||
std::size_t m_size = 0; // Number of elements, also the next index into m_dense
|
||||
std::size_t m_capacity = 0; // Number of elements + 1
|
||||
};
|
||||
|
||||
} // namespace containers
|
||||
|
||||
} // namespace kuiper
|
||||
48
include/core/application.hpp
Normal file
48
include/core/application.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
class engine;
|
||||
|
||||
struct application_spec {
|
||||
std::u8string name = u8"Kuiper Engine Application";
|
||||
std::uint32_t width = 640;
|
||||
std::uint32_t height = 480;
|
||||
bool fullscreen = false;
|
||||
};
|
||||
|
||||
class application {
|
||||
public:
|
||||
application(const application_spec& app_spec = application_spec())
|
||||
: m_app_spec(app_spec) {};
|
||||
virtual ~application() = default;
|
||||
|
||||
public:
|
||||
virtual void initialise(kuiper::engine* engine) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
virtual void pre_render(float dt) = 0;
|
||||
virtual void render(float dt) = 0;
|
||||
virtual void post_render(float dt) = 0;
|
||||
|
||||
inline const application_spec& get_app_spec() {
|
||||
return m_app_spec;
|
||||
}
|
||||
|
||||
private:
|
||||
application_spec m_app_spec = application_spec();
|
||||
};
|
||||
|
||||
/// This function must be implement by the user,
|
||||
/// defining & creating the application which will be run by the Kuiper engine.
|
||||
/// Consider this function your application's entrypoint, you can perform initialisation that must
|
||||
/// occur before your application starts here.
|
||||
/// NOTE: This function is expected to return a *unique* pointer to the application, to be owned by the engine.
|
||||
/// Don't store a copy of this pointer to use later as there are no guarantees to whether the pointer is valid.
|
||||
std::unique_ptr<application> create_application(int argc, char* argv[]);
|
||||
|
||||
} // namespace kuiper
|
||||
82
include/core/engine.hpp
Normal file
82
include/core/engine.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli/cli.hpp"
|
||||
#include "components/camera.hpp"
|
||||
#include "core/application.hpp"
|
||||
#include "graphics/opengl/framebuffer.hpp"
|
||||
#include "platform/glfw/glfw_window.hpp"
|
||||
#include "renderer/gl_renderer.hpp"
|
||||
#include "resources/resource_manager.hpp"
|
||||
|
||||
#include <flecs.h>
|
||||
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
struct engine_metrics {
|
||||
float rendering_time = 0.0f;
|
||||
gl::renderer_metrics renderer_metrics {};
|
||||
};
|
||||
|
||||
class engine {
|
||||
public:
|
||||
engine(std::unique_ptr<application>&& app);
|
||||
~engine();
|
||||
engine(const engine& other) = delete;
|
||||
engine& operator=(const engine& other) = delete;
|
||||
engine(engine&& other) noexcept = delete;
|
||||
engine& operator=(engine&& other) noexcept = delete;
|
||||
|
||||
/// Initialise core engine subsystems
|
||||
static std::expected<void, std::error_code> initialise(const cli& cli_args) noexcept;
|
||||
|
||||
void start();
|
||||
|
||||
public:
|
||||
std::shared_ptr<glfw_window> window() noexcept {
|
||||
return m_window;
|
||||
}
|
||||
|
||||
engine_metrics metrics() const noexcept {
|
||||
return m_metrics;
|
||||
}
|
||||
|
||||
flecs::world& world() noexcept {
|
||||
return m_world;
|
||||
}
|
||||
|
||||
resource_manager& resources() noexcept {
|
||||
return m_resources;
|
||||
}
|
||||
|
||||
void add_render_target(const gl::framebuffer::s_ptr& target) noexcept;
|
||||
void remove_render_target(const gl::framebuffer::s_ptr& target) noexcept;
|
||||
/// Only draw to render targets added with `add_render_target()`
|
||||
void disable_default_framebuffer() noexcept;
|
||||
/// Draw to default framebuffer as well as any additional render targets
|
||||
void enable_default_framebuffer() noexcept;
|
||||
void set_render_camera(const glm::vec3& pos, const component::camera_props& cam_props) noexcept;
|
||||
|
||||
private:
|
||||
std::unique_ptr<application> m_application {nullptr};
|
||||
std::shared_ptr<glfw_window> m_window {nullptr};
|
||||
engine_metrics m_metrics {};
|
||||
|
||||
std::vector<gl::framebuffer::s_ptr> m_render_targets {};
|
||||
bool m_render_to_default {true};
|
||||
glm::vec3 m_cam_pos {};
|
||||
component::camera_props m_cam_props {};
|
||||
|
||||
resource_manager m_resources {};
|
||||
flecs::world m_world {};
|
||||
};
|
||||
|
||||
// Some assumptions that I make
|
||||
static_assert(sizeof(char8_t) == sizeof(char));
|
||||
|
||||
} // namespace kuiper
|
||||
15
include/core/entrypoint.hpp
Normal file
15
include/core/entrypoint.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32)
|
||||
// TODO: Windows entrypoint
|
||||
#else
|
||||
/// Standard C entrypoint
|
||||
int main(int argc, char** argv);
|
||||
#endif
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
/// Actual entrypoint, as far as the engine is concerned.
|
||||
/// Once we reach the real entrypoint, `WinMain` should've converted any UTF-16 wchar strings into UTF-8 C-style strings
|
||||
int entrypoint(int argc, char** argv);
|
||||
} // namespace kuiper
|
||||
21
include/core/io.hpp
Normal file
21
include/core/io.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
class io {
|
||||
public:
|
||||
static std::string read_text_file(const std::filesystem::path& path) noexcept;
|
||||
|
||||
static std::filesystem::path get_home_dir() noexcept;
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
16
include/errors/errors.hpp
Normal file
16
include/errors/errors.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
enum class error : std::uint8_t {
|
||||
unknown,
|
||||
failed_to_load,
|
||||
resource_missing,
|
||||
resource_invalid,
|
||||
opengl_error
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
30
include/errors/exceptions.hpp
Normal file
30
include/errors/exceptions.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
namespace exceptions
|
||||
{
|
||||
class file_not_found : public std::exception {
|
||||
public:
|
||||
file_not_found(const std::filesystem::path& path)
|
||||
: m_path(path) {};
|
||||
|
||||
public:
|
||||
inline std::filesystem::path path() const noexcept {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
// Implementing std::exception
|
||||
inline const char* what() const noexcept override {
|
||||
return "file not found";
|
||||
}
|
||||
|
||||
private:
|
||||
std::filesystem::path m_path;
|
||||
};
|
||||
} // namespace exceptions
|
||||
} // namespace kuiper
|
||||
60
include/graphics/opengl/buffer.hpp
Normal file
60
include/graphics/opengl/buffer.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
class buffer {
|
||||
public:
|
||||
using s_ptr = std::shared_ptr<buffer>;
|
||||
|
||||
explicit buffer(GLuint id, GLenum target)
|
||||
: m_id(id), m_target(target) {}
|
||||
~buffer() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
buffer(const buffer& other) = delete;
|
||||
buffer& operator=(const buffer& other) = delete;
|
||||
buffer(buffer&& other) noexcept = default;
|
||||
buffer& operator=(buffer&& other) noexcept = default;
|
||||
|
||||
static s_ptr make(GLenum target) noexcept;
|
||||
|
||||
public:
|
||||
GLuint id() const noexcept {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void destroy() noexcept;
|
||||
void bind() const noexcept;
|
||||
void bind_to_location(std::uint32_t location) const noexcept;
|
||||
void upload(GLenum usage, std::size_t size, const void* data) noexcept;
|
||||
void update(std::size_t offset, std::size_t size, const void* data) noexcept;
|
||||
void set_vertex_attrib(std::uint32_t index,
|
||||
std::size_t size,
|
||||
GLenum type,
|
||||
std::size_t stride,
|
||||
std::size_t offset,
|
||||
bool normalised = false) noexcept;
|
||||
|
||||
private:
|
||||
GLuint m_id {0};
|
||||
GLenum m_target {0};
|
||||
std::size_t m_size {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
79
include/graphics/opengl/framebuffer.hpp
Normal file
79
include/graphics/opengl/framebuffer.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/opengl/texture.hpp"
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
class framebuffer {
|
||||
public:
|
||||
using s_ptr = std::shared_ptr<framebuffer>;
|
||||
|
||||
// OpenGL mandates at least 8 colour buffers
|
||||
static constexpr std::uint32_t max_colour_attachments = 8;
|
||||
using col_tex_storage = std::array<texture::s_ptr, max_colour_attachments>;
|
||||
|
||||
explicit framebuffer(GLuint id)
|
||||
: m_id(id) {}
|
||||
~framebuffer() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
framebuffer(const framebuffer& other) = delete;
|
||||
framebuffer& operator=(const framebuffer& other) = delete;
|
||||
framebuffer(framebuffer&& other) noexcept = default;
|
||||
framebuffer& operator=(framebuffer&& other) noexcept = default;
|
||||
|
||||
static s_ptr make() noexcept;
|
||||
static s_ptr make_default(std::uint32_t width, std::uint32_t height) noexcept;
|
||||
|
||||
public:
|
||||
void destroy() noexcept;
|
||||
|
||||
void bind() const noexcept;
|
||||
|
||||
void resize(std::uint32_t width, std::uint32_t height) noexcept;
|
||||
|
||||
GLuint id() const noexcept {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
const col_tex_storage& colour_tex() const noexcept {
|
||||
return m_colour_texs;
|
||||
}
|
||||
|
||||
const texture::s_ptr& depth_tex() const noexcept {
|
||||
return m_depth_tex;
|
||||
}
|
||||
|
||||
glm::u32vec2 dimensions() const noexcept {
|
||||
return {m_width, m_height};
|
||||
}
|
||||
|
||||
// Attach a colour texture to the framebuffer, optionally at a given attachment point. Takes ownership of `tex`.
|
||||
void attach_colour(texture::s_ptr&& tex, std::uint32_t attachment_point = 0) noexcept;
|
||||
// Attach a depth AND stencil texture to the framebuffer. Takes ownership of `tex`.
|
||||
void attach_depth(texture::s_ptr&& tex) noexcept;
|
||||
|
||||
private:
|
||||
GLuint m_id {0};
|
||||
col_tex_storage m_colour_texs {};
|
||||
texture::s_ptr m_depth_tex {nullptr};
|
||||
std::uint32_t m_width {0};
|
||||
std::uint32_t m_height {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
70
include/graphics/opengl/program.hpp
Normal file
70
include/graphics/opengl/program.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/ext/vector_float3.hpp"
|
||||
#include "graphics/opengl/shader.hpp"
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <glm/mat3x3.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
class program {
|
||||
public:
|
||||
using s_ptr = std::shared_ptr<program>;
|
||||
|
||||
explicit program(GLuint id)
|
||||
: m_id(id) {}
|
||||
~program() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
program(const program& other) = delete;
|
||||
program& operator=(const program& other) = delete;
|
||||
program(program&& other) noexcept = default;
|
||||
program& operator=(program&& other) noexcept = default;
|
||||
|
||||
static s_ptr make() noexcept;
|
||||
static s_ptr from_shaders(std::initializer_list<shader::s_ptr> shaders);
|
||||
|
||||
public:
|
||||
GLuint id() const noexcept {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void destroy() noexcept;
|
||||
void use() const noexcept;
|
||||
void attach_shader(const shader::s_ptr& shader) noexcept;
|
||||
bool link() noexcept;
|
||||
|
||||
// Uniforms
|
||||
|
||||
GLint uniform_location(const char* name) const noexcept;
|
||||
void set_uint(const char* uniform, std::uint32_t value) noexcept;
|
||||
void set_int(const char* uniform, std::int32_t value) noexcept;
|
||||
void set_int_array(const char* uniform, const std::int32_t* value_arr, std::size_t len) noexcept;
|
||||
void set_float(const char* uniform, float value) noexcept;
|
||||
void set_vec3(const char* uniform, const glm::vec3& value) noexcept;
|
||||
void set_uvec3(const char* uniform, const glm::uvec3& value) noexcept;
|
||||
void set_uvec2(const char* uniform, const glm::uvec2& value) noexcept;
|
||||
void set_mat3(const char* uniform, const glm::mat3& value) noexcept;
|
||||
void set_mat4(const char* uniform, const glm::mat4& value) noexcept;
|
||||
|
||||
private:
|
||||
GLuint m_id {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
74
include/graphics/opengl/render_context.hpp
Normal file
74
include/graphics/opengl/render_context.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/opengl/framebuffer.hpp"
|
||||
|
||||
#include <glad/gl.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
void initialise();
|
||||
|
||||
void dump_hardware_info();
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
void debug_callback(GLenum source,
|
||||
GLenum type,
|
||||
GLuint id,
|
||||
GLenum severity,
|
||||
GLsizei length,
|
||||
const GLchar* message,
|
||||
const void* userParam) noexcept;
|
||||
#endif
|
||||
|
||||
class render_context {
|
||||
public:
|
||||
/// Binds a framebuffer to the renderer pipeline
|
||||
static void bind_framebuffer(const framebuffer::s_ptr& fb) noexcept;
|
||||
|
||||
/// Binds the default framebuffer
|
||||
static void bind_default_framebuffer() noexcept;
|
||||
|
||||
/// Set the viewport size over the currently bound framebuffer
|
||||
static void set_viewport_size(std::uint32_t width, std::uint32_t height) noexcept;
|
||||
|
||||
/// Enables an OpenGL feature
|
||||
static void enable_feature(GLenum feature) noexcept;
|
||||
|
||||
/// Disables an OpenGL feature
|
||||
static void disable_feature(GLenum feature) noexcept;
|
||||
|
||||
template<typename T>
|
||||
static T get(GLenum param) noexcept {
|
||||
T ret {};
|
||||
|
||||
if constexpr (std::is_same<T, GLint>::value) {
|
||||
glGetIntegerv(param, &ret);
|
||||
} else if constexpr (std::is_same<T, GLint64>::value) {
|
||||
glGetInteger64v(param, &ret);
|
||||
} else if constexpr (std::is_same<T, GLfloat>::value) {
|
||||
glGetFloatv(param, &ret);
|
||||
} else if constexpr (std::is_same<T, GLdouble>::value) {
|
||||
glGetDoublev(param, &ret);
|
||||
} else if constexpr (std::is_same<T, GLboolean>::value) {
|
||||
glGetBooleanv(param, &ret);
|
||||
} else {
|
||||
static_assert(!"non-exhaustive if-constexpr!");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static constexpr const char* GLSL_VERSION = "#version 460 core";
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
57
include/graphics/opengl/shader.hpp
Normal file
57
include/graphics/opengl/shader.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
enum class shader_type : std::uint8_t {
|
||||
none = 0,
|
||||
vertex,
|
||||
fragment,
|
||||
compute
|
||||
};
|
||||
|
||||
class shader {
|
||||
public:
|
||||
using s_ptr = std::shared_ptr<shader>;
|
||||
|
||||
explicit shader(GLuint id, GLenum type)
|
||||
: m_id(id), m_type(type) {}
|
||||
~shader() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
shader(const shader& other) = delete;
|
||||
shader& operator=(const shader& other) = delete;
|
||||
shader(shader&& other) noexcept = default;
|
||||
shader& operator=(shader&& other) noexcept = default;
|
||||
|
||||
static s_ptr make(shader_type type) noexcept;
|
||||
static s_ptr from_path(const std::filesystem::path& shader_path, shader_type type = shader_type::none) noexcept;
|
||||
static s_ptr from_source(std::string_view source, shader_type type) noexcept;
|
||||
|
||||
public:
|
||||
void destroy() noexcept;
|
||||
void attach_source(std::string_view source) noexcept;
|
||||
void compile() noexcept;
|
||||
GLuint id() const noexcept {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
private:
|
||||
GLuint m_id {0};
|
||||
GLenum m_type {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
97
include/graphics/opengl/texture.hpp
Normal file
97
include/graphics/opengl/texture.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
enum class texture_type : std::uint8_t {
|
||||
none = 0,
|
||||
tex_2d
|
||||
};
|
||||
|
||||
class texture {
|
||||
public:
|
||||
using s_ptr = std::shared_ptr<texture>;
|
||||
|
||||
explicit texture(GLuint id, GLenum target)
|
||||
: m_id(id), m_target(target) {}
|
||||
~texture() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
texture(const texture& other) = delete;
|
||||
texture& operator=(const texture& other) = delete;
|
||||
texture(texture&& other) noexcept = default;
|
||||
texture& operator=(texture&& other) noexcept = default;
|
||||
|
||||
/// Makes a new, uninitialised texture
|
||||
static s_ptr make(texture_type type = texture_type::tex_2d) noexcept;
|
||||
/// Makes a placeholder 4x4 texture
|
||||
static s_ptr make_placeholder() noexcept;
|
||||
/// Makes a texture from a path to an encoded image
|
||||
static s_ptr from_image_path(const std::filesystem::path& image_path, bool flip_y = true) noexcept;
|
||||
/// Makes a texture from a pointer to encoded image data
|
||||
static s_ptr from_image_data(const std::uint8_t* buffer, std::size_t length, bool flip_y = true) noexcept;
|
||||
|
||||
public:
|
||||
/// Explicitly delete the OpenGL texture object
|
||||
void destroy() noexcept;
|
||||
/// Bind the texture
|
||||
void bind() noexcept;
|
||||
/// Upload image data to the texture object on the GPU
|
||||
void upload(
|
||||
GLint internal_fmt, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* data) noexcept;
|
||||
/// Resize underlying texture buffer
|
||||
void resize(std::uint32_t width, std::uint32_t height) noexcept;
|
||||
/// Set a texture parameter
|
||||
void set_param(GLenum name, GLint param) noexcept;
|
||||
/// Generate texture mipmaps
|
||||
void gen_mipmaps() noexcept;
|
||||
|
||||
/// Get the dimensions of the texture
|
||||
glm::uvec2 dimensions() const noexcept {
|
||||
return {m_width, m_height};
|
||||
}
|
||||
|
||||
GLint internal_format() const noexcept {
|
||||
return m_internal_fmt;
|
||||
}
|
||||
|
||||
GLenum pixel_format() const noexcept {
|
||||
return m_pixel_fmt;
|
||||
}
|
||||
|
||||
GLenum pixel_type() const noexcept {
|
||||
return m_pixel_type;
|
||||
}
|
||||
|
||||
/// Get the internal OpenGL object name
|
||||
GLuint id() const noexcept {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
private:
|
||||
GLuint m_id {0};
|
||||
GLenum m_target {0};
|
||||
std::uint32_t m_width {0};
|
||||
std::uint32_t m_height {0};
|
||||
GLint m_internal_fmt {0};
|
||||
GLenum m_pixel_fmt {0};
|
||||
GLenum m_pixel_type {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
74
include/graphics/opengl/texture_array.hpp
Normal file
74
include/graphics/opengl/texture_array.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
class texture_array {
|
||||
public:
|
||||
using s_ptr = std::shared_ptr<texture_array>;
|
||||
|
||||
explicit texture_array(GLuint id, GLenum target)
|
||||
: m_id(id), m_target(target) {}
|
||||
~texture_array() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
texture_array(const texture_array& other) = delete;
|
||||
texture_array& operator=(const texture_array& other) = delete;
|
||||
texture_array(texture_array&& other) noexcept = default;
|
||||
texture_array& operator=(texture_array&& other) noexcept = default;
|
||||
|
||||
static s_ptr make(GLenum target) noexcept;
|
||||
|
||||
public:
|
||||
void destroy() noexcept;
|
||||
void bind(std::uint32_t unit = 0) const noexcept;
|
||||
void set_storage(GLenum internal_fmt, std::uint32_t width, std::uint32_t height, std::uint32_t depth) noexcept;
|
||||
void 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 = 0) noexcept;
|
||||
void set_param(GLenum name, GLint param) noexcept;
|
||||
void gen_mipmaps() noexcept;
|
||||
|
||||
glm::uvec3 dimensions() const noexcept {
|
||||
return {m_width, m_height, m_depth};
|
||||
}
|
||||
|
||||
GLuint id() const noexcept {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
GLenum internal_format() const noexcept {
|
||||
return m_internal_fmt;
|
||||
}
|
||||
|
||||
private:
|
||||
GLuint m_id {0};
|
||||
GLenum m_target {0};
|
||||
GLenum m_internal_fmt {0};
|
||||
std::uint32_t m_width {0};
|
||||
std::uint32_t m_height {0};
|
||||
std::uint32_t m_depth {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
48
include/graphics/opengl/vertex_array.hpp
Normal file
48
include/graphics/opengl/vertex_array.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
|
||||
class vertex_array {
|
||||
public:
|
||||
using s_ptr = std::shared_ptr<vertex_array>;
|
||||
|
||||
explicit vertex_array(GLuint id)
|
||||
: m_id(id) {}
|
||||
~vertex_array() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
vertex_array(const vertex_array& other) = delete;
|
||||
vertex_array& operator=(const vertex_array& other) = delete;
|
||||
vertex_array(vertex_array&& other) noexcept = default;
|
||||
vertex_array& operator=(vertex_array&& other) noexcept = default;
|
||||
|
||||
static s_ptr make() noexcept;
|
||||
|
||||
public:
|
||||
GLuint id() const noexcept {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void destroy() noexcept;
|
||||
void bind() const noexcept;
|
||||
void enable_attrib(std::uint32_t index) noexcept;
|
||||
void disable_attrib(std::uint32_t index) noexcept;
|
||||
void set_attrib_divisor(std::uint32_t attrib, std::uint32_t divisor) noexcept;
|
||||
void set_binding_divisor(std::uint32_t binding, std::uint32_t divisor) noexcept;
|
||||
|
||||
private:
|
||||
GLuint m_id {0};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
56
include/logging/engine_logger.hpp
Normal file
56
include/logging/engine_logger.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "logging/logger.hpp"
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
/// @brief Singleton class providing engine-wide logging facilities
|
||||
class engine_logger {
|
||||
private:
|
||||
engine_logger() = default;
|
||||
|
||||
public:
|
||||
~engine_logger() = default;
|
||||
|
||||
public:
|
||||
static inline engine_logger& get(void) {
|
||||
static engine_logger s_instance;
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void trace(const char* fmt, Args... args) {
|
||||
m_log.trace(fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void debug(const char* fmt, Args... args) {
|
||||
m_log.debug(fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void info(const char* fmt, Args... args) {
|
||||
m_log.info(fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void warn(const char* fmt, Args... args) {
|
||||
m_log.warn(fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void error(const char* fmt, Args... args) {
|
||||
m_log.error(fmt, args...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void critical(const char* fmt, Args... args) {
|
||||
m_log.critical(fmt, args...);
|
||||
}
|
||||
|
||||
private:
|
||||
kuiper::logger m_log {"engine"};
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
67
include/logging/logger.hpp
Normal file
67
include/logging/logger.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view> // TODO: Use std::format_string when available
|
||||
#include <utility>
|
||||
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
class logger {
|
||||
public:
|
||||
logger()
|
||||
: m_log(spdlog::stdout_color_st("log")) {
|
||||
#if !defined(NDEBUG)
|
||||
m_log->set_level(spdlog::level::debug);
|
||||
#else
|
||||
m_log->set_level(spdlog::level::info);
|
||||
#endif
|
||||
}
|
||||
logger(const char* name)
|
||||
: m_log(spdlog::stdout_color_st(name)) {
|
||||
#if !defined(NDEBUG)
|
||||
m_log->set_level(spdlog::level::debug);
|
||||
#else
|
||||
m_log->set_level(spdlog::level::info);
|
||||
#endif
|
||||
}
|
||||
~logger() = default;
|
||||
|
||||
template<typename... Args>
|
||||
inline void trace(std::string_view fmt, Args&&... args) {
|
||||
m_log->trace(fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void debug(std::string_view fmt, Args&&... args) {
|
||||
m_log->debug(fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void info(std::string_view fmt, Args&&... args) {
|
||||
m_log->info(fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void warn(std::string_view fmt, Args&&... args) {
|
||||
m_log->warn(fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void error(std::string_view fmt, Args&&... args) {
|
||||
m_log->error(fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void critical(std::string_view fmt, Args&&... args) {
|
||||
m_log->critical(fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<spdlog::logger> m_log {nullptr};
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
18
include/maths/aspect_ratio.hpp
Normal file
18
include/maths/aspect_ratio.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace maths
|
||||
{
|
||||
template<typename T, typename V>
|
||||
requires(std::floating_point<V> || std::integral<V>) && std::floating_point<T>
|
||||
constexpr inline T aspect_ratio(V width, V height) {
|
||||
return static_cast<T>(width) / static_cast<T>(height);
|
||||
}
|
||||
|
||||
} // namespace maths
|
||||
|
||||
} // namespace kuiper
|
||||
37
include/maths/pow2.hpp
Normal file
37
include/maths/pow2.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
// https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
|
||||
inline std::uint64_t next_pow2(std::uint64_t value) {
|
||||
value--;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
value |= value >> 32;
|
||||
value++;
|
||||
value += (value == 0); // Case for if value == 0 (returns 0 as the next power of 2, which 0 is not)
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
inline std::uint32_t next_pow2(std::uint32_t value) {
|
||||
value--;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
value++;
|
||||
value += (value == 0);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace kuiper
|
||||
52
include/maths/transform.hpp
Normal file
52
include/maths/transform.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/gtc/quaternion.hpp"
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/ext/quaternion_float.hpp>
|
||||
#include <glm/ext/quaternion_trigonometric.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/trigonometric.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace maths
|
||||
{
|
||||
|
||||
struct transform_quat {
|
||||
glm::vec3 position = {0.0f, 0.0f, 0.0f};
|
||||
glm::quat rotation = {1.0f, 0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 scale = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
glm::mat4 to_mat4() const noexcept {
|
||||
static constexpr glm::mat4 identity_mat = glm::mat4(1.0f);
|
||||
|
||||
const glm::mat4 tm = glm::translate(identity_mat, position);
|
||||
const glm::mat4 rm = glm::mat4_cast(rotation);
|
||||
const glm::mat4 sm = glm::scale(identity_mat, scale);
|
||||
|
||||
return tm * rm * sm;
|
||||
}
|
||||
};
|
||||
|
||||
struct transform_euler {
|
||||
glm::vec3 position = {0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 rotation = {0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 scale = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
glm::mat4 to_mat4() const noexcept {
|
||||
static constexpr glm::mat4 identity_mat = glm::mat4(1.0f);
|
||||
|
||||
const glm::mat4 tm = glm::translate(identity_mat, position);
|
||||
const glm::mat4 rm = glm::mat4_cast(glm::quat(rotation));
|
||||
const glm::mat4 sm = glm::scale(identity_mat, scale);
|
||||
|
||||
return tm * rm * sm;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace maths
|
||||
|
||||
} // namespace kuiper
|
||||
89
include/platform/glfw/glfw_window.hpp
Normal file
89
include/platform/glfw/glfw_window.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/opengl/framebuffer.hpp"
|
||||
#include "window/input_system.hpp"
|
||||
#include "window/window.hpp"
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
class glfw_window : public window {
|
||||
public:
|
||||
glfw_window(
|
||||
std::uint32_t width, std::uint32_t height, std::u8string_view title, GLFWmonitor* monitor, GLFWwindow* share);
|
||||
~glfw_window() override;
|
||||
|
||||
// Only allow moving
|
||||
glfw_window(const glfw_window& other) = delete;
|
||||
glfw_window& operator=(const glfw_window& other) = delete;
|
||||
glfw_window(glfw_window&& other) noexcept = default;
|
||||
glfw_window& operator=(glfw_window&& other) noexcept = default;
|
||||
|
||||
public:
|
||||
// Implement window
|
||||
|
||||
virtual void* get_native_handle() const noexcept override;
|
||||
virtual void set_title(std::u8string_view title) noexcept override;
|
||||
virtual void poll_events() noexcept override;
|
||||
virtual void swap_buffers() noexcept override;
|
||||
virtual void set_swap_interval(std::uint32_t interval) noexcept override;
|
||||
virtual void bind_context() noexcept override;
|
||||
virtual gl::framebuffer::s_ptr back_buffer() const noexcept override;
|
||||
|
||||
virtual bool should_close() const noexcept override;
|
||||
virtual bool is_minimised() const noexcept override;
|
||||
virtual bool is_cursor_locked() const noexcept override;
|
||||
|
||||
virtual void lock_cursor() noexcept override;
|
||||
virtual void unlock_cursor() noexcept override;
|
||||
virtual void enable_raw_input() noexcept override;
|
||||
virtual void disable_raw_input() noexcept override;
|
||||
|
||||
virtual glm::uvec2 get_size() const noexcept override;
|
||||
virtual glm::uvec2 get_framebuffer_size() const noexcept override; // should this go in render context?
|
||||
virtual glm::vec2 get_content_scale() const noexcept override;
|
||||
|
||||
input_system& input() noexcept {
|
||||
return m_input_state;
|
||||
}
|
||||
|
||||
private:
|
||||
void resize_back_buffer() noexcept;
|
||||
|
||||
public:
|
||||
static void initialise_glfw(bool prefer_x11);
|
||||
|
||||
private:
|
||||
static GLFWwindow* create_glfw_window(std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::u8string_view title,
|
||||
GLFWmonitor* monitor,
|
||||
GLFWwindow* share) noexcept;
|
||||
static void push_window_hints() noexcept;
|
||||
|
||||
// GLFW Callbacks
|
||||
|
||||
static void glfw_error_callback(int error_code, const char* description);
|
||||
static void glfw_framebuffer_size_callback(GLFWwindow* window, int width, int height);
|
||||
static void glfw_content_scale_callback(GLFWwindow* window, float xscale, float yscale);
|
||||
static void glfw_key_event_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
static void glfw_cursor_pos_callback(GLFWwindow* window, double xpos, double ypos);
|
||||
static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
|
||||
static void glfw_dragdrop_callback(GLFWwindow* window, int path_count, const char* paths[]);
|
||||
|
||||
private:
|
||||
GLFWwindow* m_window_handle = nullptr;
|
||||
input_system m_input_state {};
|
||||
bool m_cursor_locked = false;
|
||||
gl::framebuffer::s_ptr m_default_framebuffer = nullptr;
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
163
include/renderer/gl_renderer.hpp
Normal file
163
include/renderer/gl_renderer.hpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include "components/camera.hpp"
|
||||
#include "components/lights.hpp"
|
||||
#include "graphics/opengl/buffer.hpp"
|
||||
#include "graphics/opengl/framebuffer.hpp"
|
||||
#include "graphics/opengl/program.hpp"
|
||||
#include "graphics/opengl/vertex_array.hpp"
|
||||
#include "maths/transform.hpp"
|
||||
#include "resources/material.hpp"
|
||||
#include "resources/model.hpp"
|
||||
#include "resources/primitive.hpp"
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include <array>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace gl
|
||||
{
|
||||
static constexpr std::size_t MAX_NUM_LIGHTS = 100;
|
||||
static constexpr std::size_t LIGHT_SSBO_INIT_SIZE = 1000;
|
||||
static constexpr std::uint32_t NUM_CLUSTERS_X = 12;
|
||||
static constexpr std::uint32_t NUM_CLUSTERS_Y = 12;
|
||||
static constexpr std::uint32_t NUM_CLUSTERS_Z = 24;
|
||||
static constexpr std::uint32_t NUM_CLUSTERS = NUM_CLUSTERS_X * NUM_CLUSTERS_Y * NUM_CLUSTERS_Z;
|
||||
static constexpr std::uint32_t LOCAL_SIZE = 128;
|
||||
static constexpr float NEAR_Z_DEPTH = 0.1f;
|
||||
static constexpr float FAR_Z_DEPTH = std::numeric_limits<std::uint16_t>::max();
|
||||
|
||||
struct gl_state {
|
||||
// Vertex & element buffers
|
||||
vertex_array::s_ptr vao {nullptr};
|
||||
buffer::s_ptr vertex_buffer {nullptr};
|
||||
buffer::s_ptr index_buffer {nullptr};
|
||||
|
||||
// Indirect & model matrix buffer
|
||||
|
||||
buffer::s_ptr indirect_buffer {nullptr};
|
||||
buffer::s_ptr matrix_buffer {nullptr};
|
||||
|
||||
// Shaders
|
||||
program::s_ptr pbr_shader {nullptr};
|
||||
program::s_ptr clustering_shader {nullptr};
|
||||
program::s_ptr light_culling_shader {nullptr};
|
||||
|
||||
// Lighting buffers
|
||||
buffer::s_ptr cluster_buffer {nullptr};
|
||||
buffer::s_ptr lights_buffer {nullptr};
|
||||
|
||||
// Configuration
|
||||
std::uint32_t max_texture_units {32};
|
||||
std::vector<std::int32_t> texture_unit_slots {};
|
||||
};
|
||||
|
||||
struct renderer_metrics {
|
||||
std::size_t n_vertices {};
|
||||
std::size_t n_indices {};
|
||||
std::size_t n_draw_cmds {};
|
||||
std::size_t n_matrices {};
|
||||
std::size_t n_materials {};
|
||||
std::size_t n_lights {};
|
||||
};
|
||||
|
||||
class renderer {
|
||||
public:
|
||||
using vertex_t = vertex;
|
||||
using index_t = std::uint32_t;
|
||||
|
||||
struct draw_elements_indirect_cmd {
|
||||
std::uint32_t count {0};
|
||||
std::uint32_t instance_count {0};
|
||||
std::uint32_t first_index {0};
|
||||
std::uint32_t base_vertex {0};
|
||||
std::uint32_t base_instance {0};
|
||||
};
|
||||
|
||||
struct alignas(4) render_matrices {
|
||||
glm::mat4 model_matrix;
|
||||
glm::mat3 normal_matrix;
|
||||
};
|
||||
|
||||
struct alignas(16) cluster {
|
||||
glm::vec4 min_point;
|
||||
glm::vec4 max_point;
|
||||
std::uint32_t count;
|
||||
std::uint32_t light_indices[MAX_NUM_LIGHTS];
|
||||
};
|
||||
|
||||
struct alignas(16) point_light {
|
||||
glm::vec4 position;
|
||||
glm::vec4 colour;
|
||||
float intensity;
|
||||
float radius;
|
||||
};
|
||||
|
||||
private:
|
||||
using vertex_storage_t = std::vector<vertex_t>;
|
||||
using index_storage_t = std::vector<index_t>;
|
||||
using draw_cmd_storage_t = std::vector<draw_elements_indirect_cmd>;
|
||||
using matrix_storage_t = std::vector<render_matrices>;
|
||||
using material_storage_t = std::vector<resource::material>;
|
||||
using light_storage_t = std::vector<point_light>;
|
||||
|
||||
public:
|
||||
renderer(gl_state&& state)
|
||||
: m_state(std::move(state)) {}
|
||||
~renderer() = default;
|
||||
|
||||
renderer(const renderer&) = delete;
|
||||
renderer& operator=(const renderer&) = delete;
|
||||
renderer(renderer&&) noexcept = delete;
|
||||
renderer& operator=(renderer&&) noexcept = delete;
|
||||
|
||||
static renderer init();
|
||||
|
||||
void render(const framebuffer::s_ptr& framebuffer,
|
||||
const glm::vec3& cam_pos,
|
||||
const component::camera_props& cam_props) noexcept;
|
||||
void flush() noexcept;
|
||||
renderer_metrics metrics() const noexcept;
|
||||
|
||||
void draw_model(const resource::model& model, const maths::transform_euler& transform);
|
||||
void draw_mesh(const resource::mesh& mesh,
|
||||
const std::vector<resource::material>& materials,
|
||||
const glm::mat4& transform);
|
||||
void draw_primitive(const resource::primitive& primitive,
|
||||
const resource::material& material,
|
||||
const maths::transform_euler& transform);
|
||||
void draw_primitive(const resource::primitive& primitive,
|
||||
const resource::material& material,
|
||||
const glm::mat4& transform);
|
||||
void add_light(const glm::vec3& pos, const component::point_light& light) noexcept;
|
||||
|
||||
private:
|
||||
void update_and_upload() noexcept;
|
||||
|
||||
private:
|
||||
gl_state m_state {};
|
||||
|
||||
vertex_storage_t m_vertices {};
|
||||
index_storage_t m_indices {};
|
||||
draw_cmd_storage_t m_draw_cmds {};
|
||||
matrix_storage_t m_matrices {};
|
||||
material_storage_t m_materials {};
|
||||
light_storage_t m_lights {};
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
|
||||
} // namespace kuiper
|
||||
79
include/resources/image.hpp
Normal file
79
include/resources/image.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace resource
|
||||
{
|
||||
|
||||
struct image_error {
|
||||
std::string_view message;
|
||||
};
|
||||
|
||||
class image {
|
||||
public:
|
||||
image(std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::uint32_t n_channels,
|
||||
const std::uint8_t* data,
|
||||
std::size_t len)
|
||||
: m_width(width), m_height(height), m_channels(n_channels), m_data(data, data + len) {}
|
||||
image() = default;
|
||||
~image() = default;
|
||||
image(const image& other) = default;
|
||||
image& operator=(const image& other) = default;
|
||||
image(image&& other) noexcept = default;
|
||||
image& operator=(image&& other) noexcept = default;
|
||||
|
||||
static image make_blank(std::uint32_t width, std::uint32_t height, std::uint32_t n_channels) noexcept;
|
||||
static std::expected<image, image_error> from_path(const std::filesystem::path& path,
|
||||
std::uint8_t num_channels = 4) noexcept;
|
||||
static std::expected<image, image_error> from_memory(const std::uint8_t* data,
|
||||
std::size_t len,
|
||||
std::uint8_t num_channels = 4) noexcept;
|
||||
|
||||
std::uint32_t width() const noexcept {
|
||||
return m_width;
|
||||
}
|
||||
|
||||
std::uint32_t height() const noexcept {
|
||||
return m_height;
|
||||
}
|
||||
|
||||
glm::uvec2 dimensions() const noexcept {
|
||||
return {m_width, m_height};
|
||||
}
|
||||
|
||||
std::uint32_t channels() const noexcept {
|
||||
return m_channels;
|
||||
}
|
||||
|
||||
const std::vector<std::uint8_t>& pixel_data() const noexcept {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>& pixel_data_mut() noexcept {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
void resize(std::uint32_t width, std::uint32_t height) noexcept;
|
||||
|
||||
private:
|
||||
std::uint32_t m_width {0};
|
||||
std::uint32_t m_height {0};
|
||||
std::uint32_t m_channels {0};
|
||||
std::vector<std::uint8_t> m_data {};
|
||||
};
|
||||
|
||||
} // namespace resource
|
||||
|
||||
} // namespace kuiper
|
||||
20
include/resources/material.hpp
Normal file
20
include/resources/material.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/opengl/texture.hpp"
|
||||
#include "graphics/opengl/texture_array.hpp"
|
||||
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace kuiper::resource
|
||||
{
|
||||
|
||||
struct material {
|
||||
glm::vec4 base_colour_factor {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
float metallic_factor {1.0f};
|
||||
float roughness_factor {1.0f};
|
||||
|
||||
// 0: Albedo, 1: Normal, 2: Metallic-Roughness
|
||||
gl::texture_array::s_ptr textures {nullptr};
|
||||
};
|
||||
|
||||
} // namespace kuiper::resource
|
||||
55
include/resources/mesh.hpp
Normal file
55
include/resources/mesh.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "maths/transform.hpp"
|
||||
#include "resources/primitive.hpp"
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper::resource
|
||||
{
|
||||
|
||||
class mesh {
|
||||
public:
|
||||
using primitive_storage = std::vector<primitive>;
|
||||
using submesh_storage = std::vector<mesh>;
|
||||
|
||||
mesh(const glm::mat4& t)
|
||||
: m_transform(t) {}
|
||||
|
||||
mesh(const glm::mat4& t, std::initializer_list<primitive> primitives)
|
||||
: m_transform(t) {
|
||||
for (const auto& p : primitives) {
|
||||
m_primitives.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
void add_primitive(const primitive& prim) {
|
||||
return m_primitives.push_back(prim);
|
||||
}
|
||||
|
||||
void add_submesh(const class mesh& mesh) {
|
||||
return m_submeshes.push_back(mesh);
|
||||
}
|
||||
|
||||
const glm::mat4& transform() const noexcept {
|
||||
return m_transform;
|
||||
}
|
||||
|
||||
const primitive_storage& primitives() const noexcept {
|
||||
return m_primitives;
|
||||
}
|
||||
|
||||
const submesh_storage& submeshes() const noexcept {
|
||||
return m_submeshes;
|
||||
}
|
||||
|
||||
private:
|
||||
primitive_storage m_primitives {};
|
||||
submesh_storage m_submeshes {};
|
||||
glm::mat4 m_transform {};
|
||||
};
|
||||
|
||||
} // namespace kuiper::resource
|
||||
56
include/resources/model.hpp
Normal file
56
include/resources/model.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "errors/errors.hpp"
|
||||
#include "resources/material.hpp"
|
||||
#include "resources/mesh.hpp"
|
||||
|
||||
#include <fastgltf/core.hpp>
|
||||
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper::resource
|
||||
{
|
||||
|
||||
class model {
|
||||
public:
|
||||
using mesh_storage = std::vector<mesh>;
|
||||
using material_storage = std::vector<material>;
|
||||
|
||||
model() = default;
|
||||
model(std::initializer_list<mesh> meshes) {
|
||||
for (const auto& m : meshes) {
|
||||
m_meshes.push_back(m);
|
||||
}
|
||||
}
|
||||
|
||||
void push_mesh(const mesh& m) {
|
||||
return m_meshes.push_back(m);
|
||||
}
|
||||
|
||||
void push_material(const material& m) {
|
||||
return m_materials.push_back(m);
|
||||
}
|
||||
|
||||
const mesh_storage& meshes() const noexcept {
|
||||
return m_meshes;
|
||||
}
|
||||
|
||||
const material_storage& materials() const noexcept {
|
||||
return m_materials;
|
||||
}
|
||||
|
||||
/// Construct a `model` from a path to a glTF file
|
||||
static std::expected<model, kuiper::error> from_gltf_path(const std::filesystem::path& gltf_path);
|
||||
/// Construct a `model` from a path to a glTF file, prefer this function to the above as fastgltf's `Parser` should
|
||||
/// ideally be re-used (only on one thread, though)
|
||||
static std::expected<model, kuiper::error> from_gltf_path(const std::filesystem::path& gltf_path,
|
||||
fastgltf::Parser& gltf_parser);
|
||||
|
||||
private:
|
||||
mesh_storage m_meshes;
|
||||
material_storage m_materials;
|
||||
};
|
||||
|
||||
} // namespace kuiper::resource
|
||||
38
include/resources/primitive.hpp
Normal file
38
include/resources/primitive.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
struct vertex {
|
||||
glm::vec3 position; // (x, y, z), float
|
||||
glm::vec3 normal; // (x, y, z), float
|
||||
glm::vec4 tangent; // (x, y, z, w), float
|
||||
glm::vec2 uv; // (x, y) float
|
||||
};
|
||||
|
||||
enum class primitive_type : std::uint8_t {
|
||||
none = 0,
|
||||
triangles
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
|
||||
namespace kuiper::resource
|
||||
{
|
||||
|
||||
struct primitive {
|
||||
primitive_type type {primitive_type::none};
|
||||
std::vector<vertex> vertices {};
|
||||
std::vector<std::uint32_t> indices {};
|
||||
std::size_t material_idx {};
|
||||
};
|
||||
|
||||
} // namespace kuiper::resource
|
||||
13
include/resources/resource_index.hpp
Normal file
13
include/resources/resource_index.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace kuiper::resource
|
||||
{
|
||||
|
||||
class index {
|
||||
public:
|
||||
static index from_file(const std::filesystem::path& index_path) noexcept;
|
||||
};
|
||||
|
||||
} // namespace kuiper::resource
|
||||
96
include/resources/resource_manager.hpp
Normal file
96
include/resources/resource_manager.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/opengl/shader.hpp"
|
||||
#include "graphics/opengl/texture.hpp"
|
||||
#include "resources/material.hpp"
|
||||
#include "resources/model.hpp"
|
||||
#include "utils/xoshiro256plusplus.hpp"
|
||||
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
using resource_id_t = std::uint32_t;
|
||||
|
||||
enum class resource_type : std::uint8_t {
|
||||
none = 0,
|
||||
texture,
|
||||
shader,
|
||||
model
|
||||
};
|
||||
|
||||
struct resource_metadata {
|
||||
resource_id_t id {0};
|
||||
resource_type type {resource_type::none};
|
||||
std::string name {};
|
||||
};
|
||||
|
||||
class resource_manager {
|
||||
public:
|
||||
using metadata_map_t = absl::flat_hash_map<resource_id_t, resource_metadata>;
|
||||
using texture_map_t = absl::flat_hash_map<resource_id_t, gl::texture::s_ptr>;
|
||||
using shader_map_t = absl::flat_hash_map<resource_id_t, gl::shader::s_ptr>;
|
||||
using model_map_t = absl::flat_hash_map<resource_id_t, resource::model>;
|
||||
using material_map_t = absl::flat_hash_map<resource_id_t, resource::material>;
|
||||
|
||||
template<typename T>
|
||||
resource_metadata add(T&& resource, std::string_view name = {}) {
|
||||
const auto new_id = static_cast<resource_id_t>(m_prng.next());
|
||||
resource_type type = resource_type::none;
|
||||
|
||||
if constexpr (std::same_as<T, resource::model>) {
|
||||
type = resource_type::model;
|
||||
m_models.insert({new_id, std::forward<T>(resource)});
|
||||
} else {
|
||||
static_assert(!"non-exhaustive if constexpr");
|
||||
}
|
||||
|
||||
resource_metadata metadata {new_id, type, std::string(name)};
|
||||
m_metadata.insert({new_id, metadata});
|
||||
return metadata;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> get(resource_id_t id) {
|
||||
if constexpr (std::same_as<T, resource::model>) {
|
||||
if (m_models.contains(id)) {
|
||||
return m_models.at(id);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
static_assert(!"non-exhaustive if constexpr");
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<resource_metadata> import_from_path(const std::filesystem::path& path);
|
||||
|
||||
const metadata_map_t& metadata() const noexcept {
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
const model_map_t& models() const noexcept {
|
||||
return m_models;
|
||||
}
|
||||
|
||||
private:
|
||||
random::xoshiro256pp m_prng {random::xoshiro256pp::from_random_device()};
|
||||
|
||||
metadata_map_t m_metadata {};
|
||||
texture_map_t m_textures {};
|
||||
shader_map_t m_shaders {};
|
||||
model_map_t m_models {};
|
||||
material_map_t m_materials {};
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
74
include/utils/arc.hpp
Normal file
74
include/utils/arc.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
/// Atomically reference counted type
|
||||
template<typename T>
|
||||
class arc {
|
||||
public:
|
||||
/// Argument-forwarding constructor
|
||||
template<typename... Args>
|
||||
explicit arc(Args&&... args)
|
||||
: m_inner(std::forward<Args>(args)...) {}
|
||||
|
||||
/// Default constructor (no args.)
|
||||
arc()
|
||||
: m_inner() {}
|
||||
|
||||
/// Destructor
|
||||
~arc() {
|
||||
if (m_ref_count.load() > 0)
|
||||
return;
|
||||
}
|
||||
|
||||
/// Copy constructor
|
||||
arc(const arc& other)
|
||||
: m_inner(other.m_inner), m_ref_count(other.m_ref_count.load() + 1) {}
|
||||
|
||||
/// Copy assignment constructor
|
||||
arc& operator=(const arc& other) {
|
||||
m_inner = other.m_inner;
|
||||
m_ref_count = other.m_ref_count.load() + 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Move constructor
|
||||
arc(arc&& other) noexcept
|
||||
: m_inner(std::move(other.m_inner)), m_ref_count(other.m_ref_count.load()) {};
|
||||
|
||||
/// Move assignment constructor
|
||||
arc& operator=(arc&& other) noexcept {
|
||||
*this = std::move(other);
|
||||
return *this;
|
||||
};
|
||||
|
||||
// Inner type access
|
||||
|
||||
/// Reference to the type contained in the `arc`
|
||||
T& inner() noexcept {
|
||||
return m_inner;
|
||||
}
|
||||
|
||||
/// Read-only reference to the type contained in the `arc`
|
||||
const T& const_inner() const noexcept {
|
||||
return m_inner;
|
||||
}
|
||||
|
||||
// Reference count
|
||||
|
||||
std::size_t ref_count() const noexcept {
|
||||
return m_ref_count.load();
|
||||
}
|
||||
|
||||
private:
|
||||
T m_inner;
|
||||
|
||||
std::atomic_size_t m_ref_count = 0;
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
25
include/utils/assert.hpp
Normal file
25
include/utils/assert.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <print>
|
||||
#include <stacktrace>
|
||||
#include <string>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
#define KUIPER_ASSERT(cond) \
|
||||
if (!(cond)) { \
|
||||
std::println(stderr, "Assertion failed at: {}", __PRETTY_FUNCTION__); \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define KUIPER_ASSERT_STACKTRACE(cond) \
|
||||
if (!(cond)) { \
|
||||
const auto trace = std::stacktrace::current(); \
|
||||
std::println(stderr, "Assertion failed at: {}", __PRETTY_FUNCTION__); \
|
||||
std::println(stderr, "{}", std::to_string(trace)); \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
} // namespace kuiper
|
||||
35
include/utils/bytes.hpp
Normal file
35
include/utils/bytes.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
namespace utils
|
||||
{
|
||||
namespace bits
|
||||
{
|
||||
template<typename T>
|
||||
requires std::integral<T>
|
||||
inline T set_bit_n(T value, std::uint8_t bit_n) {
|
||||
return value | (static_cast<T>(0b00000001) << (bit_n - 1));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires std::integral<T>
|
||||
inline T unset_bit_n(T value, std::uint8_t bit_n) {
|
||||
return value & ~(static_cast<T>(0b00000001) << (bit_n - 1));
|
||||
}
|
||||
} // namespace bits
|
||||
|
||||
namespace bytes
|
||||
{
|
||||
template<typename T>
|
||||
inline constexpr std::array<std::uint8_t, sizeof(T)> as_byte_array(T value) {
|
||||
return std::bit_cast<std::array<std::uint8_t, sizeof(T)>, T>(value);
|
||||
}
|
||||
} // namespace bytes
|
||||
} // namespace utils
|
||||
} // namespace kuiper
|
||||
17
include/utils/colours.hpp
Normal file
17
include/utils/colours.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace colours
|
||||
{
|
||||
constexpr const glm::vec4 middle_grey(0.737254901961f, 0.737254901961f, 0.737254901961f, 1.0f);
|
||||
constexpr const glm::vec4 dark_grey(0.1337f, 0.1337f, 0.1337f, 1.0f);
|
||||
constexpr const glm::vec4 very_dark_grey(0.05f, 0.05f, 0.05f, 1.0f);
|
||||
constexpr const glm::vec4 black(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
constexpr const glm::vec4 nothing(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
} // namespace colours
|
||||
|
||||
} // namespace kuiper
|
||||
15
include/utils/constants.hpp
Normal file
15
include/utils/constants.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
namespace constants
|
||||
{
|
||||
constexpr const char* kuiper_engine_str = "Kuiper Engine";
|
||||
constexpr const char8_t* kuiper_engine_u8str = u8"Kuiper Engine";
|
||||
constexpr const char* author_str = "Adam Macdonald";
|
||||
constexpr const char8_t* author_u8str = u8"Adam Macdonald";
|
||||
|
||||
} // namespace constants
|
||||
|
||||
} // namespace kuiper
|
||||
73
include/utils/rc.hpp
Normal file
73
include/utils/rc.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
/// Reference counted type
|
||||
template<typename T>
|
||||
class rc {
|
||||
public:
|
||||
/// Argument-forwarding constructor
|
||||
template<typename... Args>
|
||||
explicit rc(Args&&... args)
|
||||
: m_inner(std::forward<Args>(args)...) {}
|
||||
|
||||
/// Default constructor (no args.)
|
||||
rc()
|
||||
: m_inner() {}
|
||||
|
||||
/// Destructor
|
||||
~rc() {
|
||||
if (m_ref_count > 0)
|
||||
return;
|
||||
}
|
||||
|
||||
/// Copy constructor
|
||||
rc(const rc& other)
|
||||
: m_inner(other.m_inner), m_ref_count(other.m_ref_count + 1) {}
|
||||
|
||||
/// Copy assignment constructor
|
||||
rc& operator=(const rc& other) {
|
||||
m_inner = other.m_inner;
|
||||
m_ref_count = other.m_ref_count + 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Move constructor
|
||||
rc(rc&& other) noexcept
|
||||
: m_inner(std::move(other.m_inner)), m_ref_count(other.m_ref_count) {};
|
||||
|
||||
/// Move assignment constructor
|
||||
rc& operator=(rc&& other) noexcept {
|
||||
*this = std::move(other);
|
||||
return *this;
|
||||
};
|
||||
|
||||
// Inner type access
|
||||
|
||||
/// Reference to the type contained in the `rc`
|
||||
T& inner() noexcept {
|
||||
return m_inner;
|
||||
}
|
||||
|
||||
/// Read-only reference to the type contained in the `rc`
|
||||
const T& const_inner() const noexcept {
|
||||
return m_inner;
|
||||
}
|
||||
|
||||
// Reference count
|
||||
|
||||
std::size_t ref_count() const noexcept {
|
||||
return m_ref_count;
|
||||
}
|
||||
|
||||
private:
|
||||
T m_inner;
|
||||
|
||||
std::size_t m_ref_count = 0;
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
39
include/utils/uuid.hpp
Normal file
39
include/utils/uuid.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "random/random.hpp"
|
||||
|
||||
#include <cstddef> // For size_t
|
||||
#include <cstdint> // For uint64_t
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
class UUID {
|
||||
public:
|
||||
UUID()
|
||||
: m_uuid(Random::Instance().next_u64()) {}
|
||||
UUID(const std::uint64_t uuid)
|
||||
: m_uuid(uuid) {}
|
||||
UUID(const UUID&) = default;
|
||||
|
||||
operator std::uint64_t() const {
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint64_t m_uuid = 0;
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<typename T> struct hash;
|
||||
|
||||
template<> struct hash<kuiper::UUID> {
|
||||
std::size_t operator()(const kuiper::UUID& uuid) const {
|
||||
// SURELY there won't be too many collisions with 2^64 possible combinations :^)
|
||||
return static_cast<uint64_t>(uuid);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
148
include/utils/xoshiro256plusplus.hpp
Normal file
148
include/utils/xoshiro256plusplus.hpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
namespace kuiper::random
|
||||
{
|
||||
|
||||
class xoshiro256pp {
|
||||
public:
|
||||
xoshiro256pp(const std::array<std::uint64_t, 4>& state)
|
||||
: s(state) {}
|
||||
|
||||
static xoshiro256pp from_random_device() {
|
||||
std::random_device rd {};
|
||||
|
||||
static_assert(sizeof(std::random_device::result_type) == 4);
|
||||
|
||||
const std::uint64_t a = static_cast<std::uint64_t>(rd()) << 32 | static_cast<std::uint64_t>(rd());
|
||||
const std::uint64_t b = static_cast<std::uint64_t>(rd()) << 32 | static_cast<std::uint64_t>(rd());
|
||||
const std::uint64_t c = static_cast<std::uint64_t>(rd()) << 32 | static_cast<std::uint64_t>(rd());
|
||||
const std::uint64_t d = static_cast<std::uint64_t>(rd()) << 32 | static_cast<std::uint64_t>(rd());
|
||||
|
||||
return {{{a, b, c, d}}};
|
||||
}
|
||||
|
||||
public:
|
||||
std::uint64_t next() noexcept {
|
||||
const std::uint64_t result = rotl(s[0] + s[3], 23) + s[0];
|
||||
|
||||
const std::uint64_t t = s[1] << 17;
|
||||
|
||||
s[2] ^= s[0];
|
||||
s[3] ^= s[1];
|
||||
s[1] ^= s[2];
|
||||
s[0] ^= s[3];
|
||||
|
||||
s[2] ^= t;
|
||||
|
||||
s[3] = rotl(s[3], 45);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::uint64_t operator()(void) noexcept {
|
||||
return next();
|
||||
}
|
||||
|
||||
private:
|
||||
/* This is the jump function for the generator. It is equivalent
|
||||
to 2^128 calls to next(); it can be used to generate 2^128
|
||||
non-overlapping subsequences for parallel computations. */
|
||||
|
||||
void jump() noexcept {
|
||||
static constexpr std::array<std::uint64_t, 4> JUMP = {
|
||||
0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c};
|
||||
|
||||
std::uint64_t s0 = 0;
|
||||
std::uint64_t s1 = 0;
|
||||
std::uint64_t s2 = 0;
|
||||
std::uint64_t s3 = 0;
|
||||
for (int i = 0; i < JUMP.size() / sizeof(std::uint64_t); i++)
|
||||
for (int b = 0; b < 64; b++) {
|
||||
if (JUMP[i] & UINT64_C(1) << b) {
|
||||
s0 ^= s[0];
|
||||
s1 ^= s[1];
|
||||
s2 ^= s[2];
|
||||
s3 ^= s[3];
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
s[0] = s0;
|
||||
s[1] = s1;
|
||||
s[2] = s2;
|
||||
s[3] = s3;
|
||||
}
|
||||
|
||||
/* This is the long-jump function for the generator. It is equivalent to
|
||||
2^192 calls to next(); it can be used to generate 2^64 starting points,
|
||||
from each of which jump() will generate 2^64 non-overlapping
|
||||
subsequences for parallel distributed computations. */
|
||||
|
||||
void long_jump() {
|
||||
static constexpr std::array<std::uint64_t, 4> LONG_JUMP = {
|
||||
0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635};
|
||||
|
||||
std::uint64_t s0 = 0;
|
||||
std::uint64_t s1 = 0;
|
||||
std::uint64_t s2 = 0;
|
||||
std::uint64_t s3 = 0;
|
||||
for (int i = 0; i < LONG_JUMP.size() / sizeof(std::uint64_t); i++)
|
||||
for (int b = 0; b < 64; b++) {
|
||||
if (LONG_JUMP[i] & UINT64_C(1) << b) {
|
||||
s0 ^= s[0];
|
||||
s1 ^= s[1];
|
||||
s2 ^= s[2];
|
||||
s3 ^= s[3];
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
s[0] = s0;
|
||||
s[1] = s1;
|
||||
s[2] = s2;
|
||||
s[3] = s3;
|
||||
}
|
||||
|
||||
inline std::uint64_t rotl(const std::uint64_t x, int k) {
|
||||
return (x << k) | (x >> (64 - k));
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<std::uint64_t, 4> s {};
|
||||
};
|
||||
|
||||
// Original license & preface:
|
||||
|
||||
/* Written in 2019 by David Blackman and Sebastiano Vigna (vigna@acm.org)
|
||||
|
||||
To the extent possible under law, the author has dedicated all copyright
|
||||
and related and neighboring rights to this software to the public domain
|
||||
worldwide.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
||||
|
||||
/* This is xoshiro256++ 1.0, one of our all-purpose, rock-solid generators.
|
||||
It has excellent (sub-ns) speed, a state (256 bits) that is large
|
||||
enough for any parallel application, and it passes all tests we are
|
||||
aware of.
|
||||
|
||||
For generating just floating-point numbers, xoshiro256+ is even faster.
|
||||
|
||||
The state must be seeded so that it is not everywhere zero. If you have
|
||||
a 64-bit seed, we suggest to seed a splitmix64 generator and use its
|
||||
output to fill s. */
|
||||
|
||||
} // namespace kuiper::random
|
||||
76
include/window/input_system.hpp
Normal file
76
include/window/input_system.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
using action_fn_t = std::function<void()>;
|
||||
|
||||
enum action_type : std::uint8_t {
|
||||
none = 0,
|
||||
escape,
|
||||
};
|
||||
|
||||
constexpr std::size_t max_actions = 1 << (sizeof(action_type) * 8);
|
||||
|
||||
struct key_state {
|
||||
int state = GLFW_RELEASE;
|
||||
};
|
||||
|
||||
struct mouse_state {
|
||||
glm::vec2 pos;
|
||||
glm::vec2 delta_pos;
|
||||
};
|
||||
|
||||
class input_system {
|
||||
public:
|
||||
input_system() {
|
||||
// Default engine action keymappings
|
||||
m_action_keymap[GLFW_KEY_ESCAPE] = action_type::escape;
|
||||
return;
|
||||
}
|
||||
~input_system() = default;
|
||||
|
||||
// Keyboard
|
||||
|
||||
void set_key_state(int key, int state);
|
||||
int get_key_state(int key) const;
|
||||
|
||||
bool is_key_pressed(int key) const;
|
||||
bool is_key_released(int key) const;
|
||||
bool is_key_held(int key) const;
|
||||
bool is_key_down(int key) const;
|
||||
|
||||
// Mouse
|
||||
|
||||
void set_mouse_pos(const glm::vec2& pos);
|
||||
void set_mouse_delta(const glm::vec2& pos);
|
||||
|
||||
/// Get mouse position (top-left corner is the origin)
|
||||
glm::vec2 get_mouse_pos() const;
|
||||
/// Get change in mouse position since last frame (top-left corner is the origin)
|
||||
glm::vec2 get_mouse_delta() const;
|
||||
|
||||
// Actions
|
||||
// TODO: user can add a new custom action of their own
|
||||
|
||||
void subscribe_to_action(action_type type, const action_fn_t& callback);
|
||||
|
||||
private:
|
||||
std::array<key_state, GLFW_KEY_LAST> m_key_states {};
|
||||
std::array<action_type, GLFW_KEY_LAST> m_action_keymap {};
|
||||
std::array<std::vector<action_fn_t>, max_actions> m_action_listeners {};
|
||||
mouse_state m_mouse_state {};
|
||||
glm::vec2 m_old_mouse_pos {};
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
40
include/window/window.hpp
Normal file
40
include/window/window.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/opengl/framebuffer.hpp"
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace kuiper
|
||||
{
|
||||
|
||||
class window {
|
||||
public:
|
||||
virtual ~window() = default;
|
||||
|
||||
virtual void* get_native_handle() const noexcept = 0;
|
||||
virtual void set_title(std::u8string_view title) noexcept = 0;
|
||||
virtual void poll_events() noexcept = 0;
|
||||
virtual void swap_buffers() noexcept = 0;
|
||||
virtual void set_swap_interval(std::uint32_t interval) noexcept = 0;
|
||||
virtual void bind_context() noexcept = 0;
|
||||
virtual gl::framebuffer::s_ptr back_buffer() const noexcept = 0;
|
||||
|
||||
virtual bool should_close() const noexcept = 0;
|
||||
virtual bool is_minimised() const noexcept = 0;
|
||||
virtual bool is_cursor_locked() const noexcept = 0;
|
||||
|
||||
virtual void lock_cursor() noexcept = 0;
|
||||
virtual void unlock_cursor() noexcept = 0;
|
||||
virtual void enable_raw_input() noexcept = 0;
|
||||
virtual void disable_raw_input() noexcept = 0;
|
||||
|
||||
virtual glm::uvec2 get_size() const noexcept = 0;
|
||||
virtual glm::uvec2 get_framebuffer_size() const noexcept = 0; // should this go in render context?
|
||||
virtual glm::vec2 get_content_scale() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace kuiper
|
||||
Reference in New Issue
Block a user