Compare commits
8 commits
dd0f8ebf0a
...
8063903344
| Author | SHA1 | Date | |
|---|---|---|---|
| 8063903344 | |||
| f465f152e3 | |||
| d66ef55cc8 | |||
| e77a42cf7f | |||
| 53dd008df5 | |||
| d924d14ab0 | |||
| b6834310a7 | |||
| a58b0c030f |
104 changed files with 903 additions and 236 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
# engine
|
# engine
|
||||||
add_subdirectory(./base)
|
add_subdirectory(./base)
|
||||||
|
add_subdirectory(./memory)
|
||||||
add_subdirectory(./time)
|
add_subdirectory(./time)
|
||||||
add_subdirectory(./logger)
|
add_subdirectory(./logger)
|
||||||
add_subdirectory(./debug)
|
add_subdirectory(./debug)
|
||||||
|
|
@ -14,7 +15,7 @@ add_subdirectory(./input)
|
||||||
# add_subdirectory(./ui)
|
# add_subdirectory(./ui)
|
||||||
#
|
#
|
||||||
add_subdirectory(./surface)
|
add_subdirectory(./surface)
|
||||||
# add_subdirectory(./renderer)
|
add_subdirectory(./renderer)
|
||||||
add_subdirectory(./ecs)
|
add_subdirectory(./ecs)
|
||||||
#
|
#
|
||||||
add_subdirectory(./app)
|
add_subdirectory(./app)
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,16 @@ void Application::game_loop()
|
||||||
{
|
{
|
||||||
for (auto &system : m_systems)
|
for (auto &system : m_systems)
|
||||||
{
|
{
|
||||||
if (system->tick())
|
const auto &last_tick = system->get_last_tick_result();
|
||||||
{
|
const auto now = std::chrono::steady_clock::now();
|
||||||
return;
|
|
||||||
}
|
system->tick(
|
||||||
|
TickInfo {
|
||||||
|
.delta_time = now - last_tick.end_time,
|
||||||
|
.budget = std::chrono::milliseconds { 10 },
|
||||||
|
.start_time = now,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &system : m_systems_to_be_registered)
|
for (auto &system : m_systems_to_be_registered)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,89 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
namespace lt::app {
|
namespace lt::app {
|
||||||
|
|
||||||
|
/** Information required to tick a system.
|
||||||
|
* @note May be used across an entire application-frame (consisting of multiple systems ticking)
|
||||||
|
*/
|
||||||
|
struct TickInfo
|
||||||
|
{
|
||||||
|
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||||
|
|
||||||
|
using Duration_T = std::chrono::duration<double>;
|
||||||
|
|
||||||
|
/** Duration since previous tick's end_time to current tick's start_time. */
|
||||||
|
Duration_T delta_time {};
|
||||||
|
|
||||||
|
/** Maximum duration the system is expected to finish ticking in.
|
||||||
|
*
|
||||||
|
* if end_time - start_time > budget -> the system exceeded its ticking budget.
|
||||||
|
* else end_time - start_time < budget -> the system ticked properly.
|
||||||
|
*
|
||||||
|
* In other words, end_time is expected to be less than start_time + budget.
|
||||||
|
*/
|
||||||
|
Duration_T budget {};
|
||||||
|
|
||||||
|
/** Exact time which ticking started. */
|
||||||
|
Timepoint_T start_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Information about how a system's tick performed */
|
||||||
|
struct TickResult
|
||||||
|
{
|
||||||
|
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||||
|
|
||||||
|
using Duration_T = std::chrono::duration<double>;
|
||||||
|
|
||||||
|
/** The info supplied to the system for ticking. */
|
||||||
|
TickInfo info;
|
||||||
|
|
||||||
|
/** Equivalent to end_time - info.start_time. */
|
||||||
|
Duration_T duration {};
|
||||||
|
|
||||||
|
/** Exact time which ticking ended. */
|
||||||
|
Timepoint_T end_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct SystemDiagnosis
|
||||||
|
{
|
||||||
|
enum class Severity : uint8_t
|
||||||
|
{
|
||||||
|
verbose,
|
||||||
|
info,
|
||||||
|
warning,
|
||||||
|
error,
|
||||||
|
fatal,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
std::string code;
|
||||||
|
|
||||||
|
Severity severity;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SystemStats
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void push_diagnosis(SystemDiagnosis &&diagnosis)
|
||||||
|
{
|
||||||
|
auto diag = m_diagnosis.emplace_back(std::move(diagnosis));
|
||||||
|
|
||||||
|
log_dbg("message: {}", diag.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto empty_diagnosis() const -> bool
|
||||||
|
{
|
||||||
|
return m_diagnosis.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<SystemDiagnosis> m_diagnosis;
|
||||||
|
};
|
||||||
|
|
||||||
class ISystem
|
class ISystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -21,7 +103,9 @@ public:
|
||||||
|
|
||||||
virtual void on_unregister() = 0;
|
virtual void on_unregister() = 0;
|
||||||
|
|
||||||
virtual auto tick() -> bool = 0;
|
virtual void tick(TickInfo tick) = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto get_last_tick_result() const -> const TickResult & = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lt::app
|
} // namespace lt::app
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using lt::test::expect_eq;
|
||||||
using lt::test::expect_false;
|
using lt::test::expect_false;
|
||||||
using lt::test::expect_true;
|
using lt::test::expect_true;
|
||||||
|
|
||||||
using lt::ecs::Entity;
|
using lt::ecs::EntityId;
|
||||||
using lt::ecs::Registry;
|
using lt::ecs::Registry;
|
||||||
|
|
||||||
struct Component
|
struct Component
|
||||||
|
|
@ -86,7 +86,7 @@ Suite raii = [] {
|
||||||
Suite entity_raii = [] {
|
Suite entity_raii = [] {
|
||||||
Case { "create_entity returns unique values" } = [] {
|
Case { "create_entity returns unique values" } = [] {
|
||||||
auto registry = Registry {};
|
auto registry = Registry {};
|
||||||
auto set = std::unordered_set<Entity> {};
|
auto set = std::unordered_set<EntityId> {};
|
||||||
|
|
||||||
for (auto idx : std::views::iota(0, 10'000))
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
{
|
{
|
||||||
|
|
@ -101,7 +101,7 @@ Suite entity_raii = [] {
|
||||||
Case { "post create/destroy_entity has correct state" } = [] {
|
Case { "post create/destroy_entity has correct state" } = [] {
|
||||||
auto registry = Registry {};
|
auto registry = Registry {};
|
||||||
|
|
||||||
auto entities = std::vector<Entity> {};
|
auto entities = std::vector<EntityId> {};
|
||||||
for (auto idx : std::views::iota(0, 10'000))
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
{
|
{
|
||||||
entities.emplace_back(registry.create_entity());
|
entities.emplace_back(registry.create_entity());
|
||||||
|
|
@ -154,14 +154,18 @@ Suite component_raii = [] {
|
||||||
Suite callbacks = [] {
|
Suite callbacks = [] {
|
||||||
Case { "connecting on_construct/on_destruct won't throw" } = [] {
|
Case { "connecting on_construct/on_destruct won't throw" } = [] {
|
||||||
auto registry = Registry {};
|
auto registry = Registry {};
|
||||||
registry.connect_on_construct<Component>([&](Registry &, Entity) {});
|
registry.connect_on_construct<Component>([&](Registry &, EntityId) {});
|
||||||
registry.connect_on_destruct<Component>([&](Registry &, Entity) {});
|
registry.connect_on_destruct<Component>([&](Registry &, EntityId) {});
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
|
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
|
||||||
auto registry = Registry {};
|
auto registry = Registry {};
|
||||||
registry.connect_on_construct<Component>([&](Registry &, Entity) { expect_unreachable(); });
|
registry.connect_on_construct<Component>([&](Registry &, EntityId) {
|
||||||
registry.connect_on_destruct<Component>([&](Registry &, Entity) { expect_unreachable(); });
|
expect_unreachable();
|
||||||
|
});
|
||||||
|
registry.connect_on_destruct<Component>([&](Registry &, EntityId) {
|
||||||
|
expect_unreachable();
|
||||||
|
});
|
||||||
|
|
||||||
for (auto idx : std::views::iota(0, 100'000))
|
for (auto idx : std::views::iota(0, 100'000))
|
||||||
{
|
{
|
||||||
|
|
@ -171,14 +175,14 @@ Suite callbacks = [] {
|
||||||
|
|
||||||
Case { "on_construct/on_destruct gets called" } = [] {
|
Case { "on_construct/on_destruct gets called" } = [] {
|
||||||
auto registry = Registry {};
|
auto registry = Registry {};
|
||||||
auto all_entities = std::vector<Entity> {};
|
auto all_entities = std::vector<EntityId> {};
|
||||||
auto on_construct_called = std::vector<Entity> {};
|
auto on_construct_called = std::vector<EntityId> {};
|
||||||
auto on_destruct_called = std::vector<Entity> {};
|
auto on_destruct_called = std::vector<EntityId> {};
|
||||||
|
|
||||||
registry.connect_on_construct<Component>([&](Registry &, Entity entity) {
|
registry.connect_on_construct<Component>([&](Registry &, EntityId entity) {
|
||||||
on_construct_called.emplace_back(entity);
|
on_construct_called.emplace_back(entity);
|
||||||
});
|
});
|
||||||
registry.connect_on_destruct<Component>([&](Registry &, Entity entity) {
|
registry.connect_on_destruct<Component>([&](Registry &, EntityId entity) {
|
||||||
on_destruct_called.emplace_back(entity);
|
on_destruct_called.emplace_back(entity);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -206,8 +210,8 @@ Suite each = [] {
|
||||||
|
|
||||||
auto shared_entity_counter = 0u;
|
auto shared_entity_counter = 0u;
|
||||||
|
|
||||||
auto component_map_a = std::unordered_map<Entity, Component> {};
|
auto component_map_a = std::unordered_map<EntityId, Component> {};
|
||||||
auto entities_a = std::vector<Entity> {};
|
auto entities_a = std::vector<EntityId> {};
|
||||||
|
|
||||||
for (auto idx : std::views::iota(0, 10'000))
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
{
|
{
|
||||||
|
|
@ -220,10 +224,10 @@ Suite each = [] {
|
||||||
component_map_a[entity] = component;
|
component_map_a[entity] = component;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto component_map_b = std::unordered_map<lt::ecs::Entity, Component_B> {};
|
auto component_map_b = std::unordered_map<lt::ecs::EntityId, Component_B> {};
|
||||||
for (auto idx : std::views::iota(0, 10'000))
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
{
|
{
|
||||||
auto entity = Entity {};
|
auto entity = EntityId {};
|
||||||
if (idx % 3 == 0)
|
if (idx % 3 == 0)
|
||||||
{
|
{
|
||||||
entity = entities_a[idx];
|
entity = entities_a[idx];
|
||||||
|
|
@ -243,7 +247,7 @@ Suite each = [] {
|
||||||
|
|
||||||
Case { "each one element" } = [&] {
|
Case { "each one element" } = [&] {
|
||||||
auto counter = 0u;
|
auto counter = 0u;
|
||||||
registry.each<Component>([&](Entity entity, Component &component) {
|
registry.each<Component>([&](EntityId entity, Component &component) {
|
||||||
++counter;
|
++counter;
|
||||||
expect_eq(component_map_a[entity], component);
|
expect_eq(component_map_a[entity], component);
|
||||||
});
|
});
|
||||||
|
|
@ -251,7 +255,7 @@ Suite each = [] {
|
||||||
expect_eq(component_map_a.size(), counter);
|
expect_eq(component_map_a.size(), counter);
|
||||||
|
|
||||||
counter = 0u;
|
counter = 0u;
|
||||||
registry.each<Component_B>([&](Entity entity, Component_B &component) {
|
registry.each<Component_B>([&](EntityId entity, Component_B &component) {
|
||||||
++counter;
|
++counter;
|
||||||
expect_eq(component_map_b[entity], component);
|
expect_eq(component_map_b[entity], component);
|
||||||
});
|
});
|
||||||
|
|
@ -261,7 +265,7 @@ Suite each = [] {
|
||||||
Case { "each two element" } = [&] {
|
Case { "each two element" } = [&] {
|
||||||
auto counter = 0u;
|
auto counter = 0u;
|
||||||
registry.each<Component, Component_B>(
|
registry.each<Component, Component_B>(
|
||||||
[&](Entity entity, Component &component_a, Component_B &component_b) {
|
[&](EntityId entity, Component &component_a, Component_B &component_b) {
|
||||||
expect_eq(component_map_a[entity], component_a);
|
expect_eq(component_map_a[entity], component_a);
|
||||||
expect_eq(component_map_b[entity], component_b);
|
expect_eq(component_map_b[entity], component_b);
|
||||||
++counter;
|
++counter;
|
||||||
|
|
@ -277,8 +281,8 @@ Suite views = [] {
|
||||||
|
|
||||||
auto shared_entity_counter = 0u;
|
auto shared_entity_counter = 0u;
|
||||||
|
|
||||||
auto component_map_a = std::unordered_map<Entity, Component> {};
|
auto component_map_a = std::unordered_map<EntityId, Component> {};
|
||||||
auto entities_a = std::vector<Entity> {};
|
auto entities_a = std::vector<EntityId> {};
|
||||||
|
|
||||||
for (auto idx : std::views::iota(0, 10'000))
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
{
|
{
|
||||||
|
|
@ -291,10 +295,10 @@ Suite views = [] {
|
||||||
component_map_a[entity] = component;
|
component_map_a[entity] = component;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto component_map_b = std::unordered_map<Entity, Component_B> {};
|
auto component_map_b = std::unordered_map<EntityId, Component_B> {};
|
||||||
for (auto idx : std::views::iota(0, 10'000))
|
for (auto idx : std::views::iota(0, 10'000))
|
||||||
{
|
{
|
||||||
auto entity = Entity {};
|
auto entity = EntityId {};
|
||||||
if (idx % 3 == 0)
|
if (idx % 3 == 0)
|
||||||
{
|
{
|
||||||
entity = entities_a[idx];
|
entity = entities_a[idx];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,42 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace lt {
|
#include <ecs/registry.hpp>
|
||||||
|
|
||||||
} // namespace lt
|
namespace lt::ecs {
|
||||||
|
|
||||||
|
/** High-level entity convenience wrapper */
|
||||||
|
class Entity
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Entity(Ref<Registry> registry, EntityId identifier)
|
||||||
|
: m_registry(std::move(registry))
|
||||||
|
, m_identifier(identifier)
|
||||||
|
{
|
||||||
|
ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto get() -> Component_T &
|
||||||
|
{
|
||||||
|
return m_registry->get<Component_T>(m_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto get() const -> const Component_T &
|
||||||
|
{
|
||||||
|
return m_registry->get<Component_T>(m_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto get_registry() -> Ref<Registry>
|
||||||
|
{
|
||||||
|
return m_registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ref<Registry> m_registry;
|
||||||
|
|
||||||
|
EntityId m_identifier;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace lt::ecs
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
namespace lt::ecs {
|
namespace lt::ecs {
|
||||||
|
|
||||||
using Entity = uint32_t;
|
using EntityId = uint32_t;
|
||||||
|
|
||||||
constexpr auto null_entity = std::numeric_limits<Entity>::max();
|
constexpr auto null_entity = std::numeric_limits<EntityId>::max();
|
||||||
|
|
||||||
/** A registry of components, the heart of an ECS architecture.
|
/** A registry of components, the heart of an ECS architecture.
|
||||||
*
|
*
|
||||||
|
|
@ -24,9 +24,9 @@ constexpr auto null_entity = std::numeric_limits<Entity>::max();
|
||||||
class Registry
|
class Registry
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using UnderlyingSparseSet_T = TypeErasedSparseSet<Entity>;
|
using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>;
|
||||||
|
|
||||||
using Callback_T = std::function<void(Registry &, Entity)>;
|
using Callback_T = std::function<void(Registry &, EntityId)>;
|
||||||
|
|
||||||
template<typename Component_T>
|
template<typename Component_T>
|
||||||
void connect_on_construct(Callback_T callback)
|
void connect_on_construct(Callback_T callback)
|
||||||
|
|
@ -52,13 +52,13 @@ public:
|
||||||
m_on_destruct_hooks.erase(get_type_id<Component_T>());
|
m_on_destruct_hooks.erase(get_type_id<Component_T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto create_entity() -> Entity
|
auto create_entity() -> EntityId
|
||||||
{
|
{
|
||||||
++m_entity_count;
|
++m_entity_count;
|
||||||
return m_current++;
|
return m_current++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy_entity(Entity entity)
|
void destroy_entity(EntityId entity)
|
||||||
{
|
{
|
||||||
for (const auto &[key, set] : m_sparsed_sets)
|
for (const auto &[key, set] : m_sparsed_sets)
|
||||||
{
|
{
|
||||||
|
|
@ -69,14 +69,21 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Component_T>
|
template<typename Component_T>
|
||||||
auto get(Entity entity) -> Component_T &
|
auto get(EntityId entity) const -> const Component_T &
|
||||||
{
|
{
|
||||||
auto &derived_set = get_derived_set<Component_T>();
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
return derived_set.at(entity).second;
|
return derived_set.at(entity).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Component_T>
|
template<typename Component_T>
|
||||||
auto add(Entity entity, Component_T component) -> Component_T &
|
auto get(EntityId entity) -> Component_T &
|
||||||
|
{
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
return derived_set.at(entity).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto add(EntityId entity, Component_T component) -> Component_T &
|
||||||
{
|
{
|
||||||
auto &derived_set = get_derived_set<Component_T>();
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
auto &added_component = derived_set.insert(entity, std::move(component)).second;
|
auto &added_component = derived_set.insert(entity, std::move(component)).second;
|
||||||
|
|
@ -90,7 +97,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Component_T>
|
template<typename Component_T>
|
||||||
void remove(Entity entity)
|
void remove(EntityId entity)
|
||||||
{
|
{
|
||||||
if (m_on_destruct_hooks.contains(get_type_id<Component_T>()))
|
if (m_on_destruct_hooks.contains(get_type_id<Component_T>()))
|
||||||
{
|
{
|
||||||
|
|
@ -102,18 +109,18 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Component_T>
|
template<typename Component_T>
|
||||||
auto view() -> SparseSet<Component_T, Entity> &
|
auto view() -> SparseSet<Component_T, EntityId> &
|
||||||
{
|
{
|
||||||
return get_derived_set<Component_T>();
|
return get_derived_set<Component_T>();
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename ComponentA_T, typename ComponentB_T>
|
template<typename ComponentA_T, typename ComponentB_T>
|
||||||
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||||
auto view() -> std::vector<std::tuple<Entity, ComponentA_T &, ComponentB_T &>>
|
auto view() -> std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>>
|
||||||
{
|
{
|
||||||
auto &set_a = get_derived_set<ComponentA_T>();
|
auto &set_a = get_derived_set<ComponentA_T>();
|
||||||
auto &set_b = get_derived_set<ComponentB_T>();
|
auto &set_b = get_derived_set<ComponentB_T>();
|
||||||
auto view = std::vector<std::tuple<Entity, ComponentA_T &, ComponentB_T &>> {};
|
auto view = std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>> {};
|
||||||
|
|
||||||
/* iterate over the "smaller" component-set, and check if its entities have the other
|
/* iterate over the "smaller" component-set, and check if its entities have the other
|
||||||
* component */
|
* component */
|
||||||
|
|
@ -142,7 +149,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Component_T>
|
template<typename Component_T>
|
||||||
void each(std::function<void(Entity, Component_T &)> functor)
|
void each(std::function<void(EntityId, Component_T &)> functor)
|
||||||
{
|
{
|
||||||
for (auto &[entity, component] : get_derived_set<Component_T>())
|
for (auto &[entity, component] : get_derived_set<Component_T>())
|
||||||
{
|
{
|
||||||
|
|
@ -152,7 +159,7 @@ public:
|
||||||
|
|
||||||
template<typename ComponentA_T, typename ComponentB_T>
|
template<typename ComponentA_T, typename ComponentB_T>
|
||||||
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||||
void each(std::function<void(Entity, ComponentA_T &, ComponentB_T &)> functor)
|
void each(std::function<void(EntityId, ComponentA_T &, ComponentB_T &)> functor)
|
||||||
{
|
{
|
||||||
auto &set_a = get_derived_set<ComponentA_T>();
|
auto &set_a = get_derived_set<ComponentA_T>();
|
||||||
auto &set_b = get_derived_set<ComponentB_T>();
|
auto &set_b = get_derived_set<ComponentB_T>();
|
||||||
|
|
@ -223,22 +230,22 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
auto get_derived_set() -> SparseSet<T, Entity> &
|
auto get_derived_set() -> SparseSet<T, EntityId> &
|
||||||
{
|
{
|
||||||
constexpr auto type_id = get_type_id<T>();
|
constexpr auto type_id = get_type_id<T>();
|
||||||
if (!m_sparsed_sets.contains(type_id))
|
if (!m_sparsed_sets.contains(type_id))
|
||||||
{
|
{
|
||||||
m_sparsed_sets[type_id] = create_scope<SparseSet<T, Entity>>();
|
m_sparsed_sets[type_id] = create_scope<SparseSet<T, EntityId>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *base_set = m_sparsed_sets[type_id].get();
|
auto *base_set = m_sparsed_sets[type_id].get();
|
||||||
auto *derived_set = dynamic_cast<SparseSet<T, Entity> *>(base_set);
|
auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set);
|
||||||
ensure(derived_set, "Failed to downcast to derived set");
|
ensure(derived_set, "Failed to downcast to derived set");
|
||||||
|
|
||||||
return *derived_set;
|
return *derived_set;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity m_current;
|
EntityId m_current;
|
||||||
|
|
||||||
TypeId m_entity_count;
|
TypeId m_entity_count;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,11 @@ public:
|
||||||
return m_dense.end();
|
return m_dense.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto at(Identifier_T identifier) const -> const Dense_T &
|
||||||
|
{
|
||||||
|
return m_dense.at(m_sparse.at(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto at(Identifier_T identifier) -> Dense_T &
|
[[nodiscard]] auto at(Identifier_T identifier) -> Dense_T &
|
||||||
{
|
{
|
||||||
return m_dense.at(m_sparse.at(identifier));
|
return m_dense.at(m_sparse.at(identifier));
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||||
ensure(m_registry, "Failed to initialize input system: null registry");
|
ensure(m_registry, "Failed to initialize input system: null registry");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::tick() -> bool
|
void System::tick(app::TickInfo tick)
|
||||||
{
|
{
|
||||||
for (auto &[entity, surface] : m_registry->view<surface::SurfaceComponent>())
|
for (auto &[entity, surface] : m_registry->view<surface::SurfaceComponent>())
|
||||||
{
|
{
|
||||||
|
|
@ -51,7 +51,12 @@ auto System::tick() -> bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
m_last_tick_result = app::TickResult {
|
||||||
|
.info = tick,
|
||||||
|
.duration = now - tick.start_time,
|
||||||
|
.end_time = now,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::on_register()
|
void System::on_register()
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,15 @@ using test::expect_throw;
|
||||||
using test::Suite;
|
using test::Suite;
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
|
||||||
|
[[nodiscard]] auto tick_info() -> app::TickInfo
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.delta_time = std::chrono::milliseconds { 16 },
|
||||||
|
.budget = std::chrono::milliseconds { 10 },
|
||||||
|
.start_time = std::chrono::steady_clock::now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class Fixture
|
class Fixture
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -26,7 +35,7 @@ public:
|
||||||
return m_registry;
|
return m_registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto add_input_component() -> ecs::Entity
|
auto add_input_component() -> ecs::EntityId
|
||||||
{
|
{
|
||||||
auto entity = m_registry->create_entity();
|
auto entity = m_registry->create_entity();
|
||||||
m_registry->add<InputComponent>(entity, {});
|
m_registry->add<InputComponent>(entity, {});
|
||||||
|
|
@ -34,7 +43,7 @@ public:
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto add_surface_component() -> ecs::Entity
|
auto add_surface_component() -> ecs::EntityId
|
||||||
{
|
{
|
||||||
auto entity = m_registry->create_entity();
|
auto entity = m_registry->create_entity();
|
||||||
m_registry->add<surface::SurfaceComponent>(
|
m_registry->add<surface::SurfaceComponent>(
|
||||||
|
|
@ -124,7 +133,7 @@ Suite tick = [] {
|
||||||
auto registry = fixture.registry();
|
auto registry = fixture.registry();
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
expect_false(system.tick());
|
system.tick(tick_info());
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "Tick triggers input action" } = [] {
|
Case { "Tick triggers input action" } = [] {
|
||||||
|
|
@ -146,23 +155,23 @@ Suite tick = [] {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||||
|
|
||||||
surface.push_event(surface::KeyPressedEvent(69));
|
surface.push_event(surface::KeyPressedEvent(69));
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
|
||||||
|
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||||
|
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||||
|
|
||||||
surface.push_event(surface::KeyReleasedEvent(69));
|
surface.push_event(surface::KeyReleasedEvent(69));
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,17 @@ class System: public app::ISystem
|
||||||
public:
|
public:
|
||||||
System(Ref<ecs::Registry> registry);
|
System(Ref<ecs::Registry> registry);
|
||||||
|
|
||||||
auto tick() -> bool override;
|
void tick(app::TickInfo tick) override;
|
||||||
|
|
||||||
void on_register() override;
|
void on_register() override;
|
||||||
|
|
||||||
void on_unregister() override;
|
void on_unregister() override;
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||||
|
{
|
||||||
|
return m_last_tick_result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handle_event(const surface::SurfaceComponent::Event &event);
|
void handle_event(const surface::SurfaceComponent::Event &event);
|
||||||
|
|
||||||
|
|
@ -41,6 +46,8 @@ private:
|
||||||
std::array<bool, 512> m_buttons {};
|
std::array<bool, 512> m_buttons {};
|
||||||
|
|
||||||
math::vec2 m_pointer_position;
|
math::vec2 m_pointer_position;
|
||||||
|
|
||||||
|
app::TickResult m_last_tick_result {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
1
modules/memory/CMakeLists.txt
Normal file
1
modules/memory/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
add_library_module(memory)
|
||||||
73
modules/memory/public/pointer_types/null_on_move.hpp
Normal file
73
modules/memory/public/pointer_types/null_on_move.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace lt::memory {
|
||||||
|
|
||||||
|
/** Holds an `Underlying_T`, assigns it to `null_value` when this object is moved.
|
||||||
|
*
|
||||||
|
* @note For avoiding the need to explicitly implement the move constructor for objects that hold
|
||||||
|
* Vulkan objects. But may server other purposes, hence why I kept the implementation generic.
|
||||||
|
*/
|
||||||
|
template<typename Underlying_T, Underlying_T null_value = nullptr>
|
||||||
|
class NullOnMove
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NullOnMove() = default;
|
||||||
|
|
||||||
|
NullOnMove(Underlying_T value): m_value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~NullOnMove() = default;
|
||||||
|
|
||||||
|
NullOnMove(const NullOnMove &) = delete;
|
||||||
|
|
||||||
|
auto operator=(const NullOnMove &) -> NullOnMove & = delete;
|
||||||
|
|
||||||
|
NullOnMove(NullOnMove &&other) noexcept
|
||||||
|
{
|
||||||
|
*this = std::move(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator=(NullOnMove &&other) noexcept -> NullOnMove &
|
||||||
|
{
|
||||||
|
if (this->m_value == other.m_value)
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_value = other.m_value;
|
||||||
|
other.m_value = null_value;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator&() const -> const Underlying_T *
|
||||||
|
{
|
||||||
|
return &m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator&() -> Underlying_T *
|
||||||
|
{
|
||||||
|
return &m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return m_value != null_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator Underlying_T() const
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator Underlying_T()
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Underlying_T m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::memory
|
||||||
28
modules/memory/public/pointer_types/reference.hpp
Normal file
28
modules/memory/public/pointer_types/reference.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace lt::memory {
|
||||||
|
|
||||||
|
/** Wrapper around std::shared_ptr. */
|
||||||
|
template<typename t>
|
||||||
|
using Ref = std::shared_ptr<t>;
|
||||||
|
|
||||||
|
/** Allocates memory for an `Underlying_T` and directly constructs it there.
|
||||||
|
*
|
||||||
|
* @return A Ref<Underlying_T> to the constructed object.
|
||||||
|
*/
|
||||||
|
template<typename Underlying_T, typename... Args>
|
||||||
|
constexpr Ref<Underlying_T> create_ref(Args &&...args)
|
||||||
|
{
|
||||||
|
return std::make_shared<Underlying_T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts c-style pointer of type `Underlying_T` to a `Ref<Underlying_T>`. */
|
||||||
|
template<typename Underlying_T>
|
||||||
|
constexpr Ref<Underlying_T> make_ref(Underlying_T *raw_pointer)
|
||||||
|
{
|
||||||
|
return Ref<Underlying_T>(raw_pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lt::memory
|
||||||
28
modules/memory/public/pointer_types/scoped.hpp
Normal file
28
modules/memory/public/pointer_types/scoped.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace lt::memory {
|
||||||
|
|
||||||
|
/** Wrapper around std::unique_ptr. */
|
||||||
|
template<typename t>
|
||||||
|
using Scope = std::unique_ptr<t>;
|
||||||
|
|
||||||
|
/** Allocates memory for an `Underlying_T` and directly constructs it there.
|
||||||
|
*
|
||||||
|
* @return A Scope<Underlying_T> to the constructed object.
|
||||||
|
*/
|
||||||
|
template<typename Underlying_T, typename... Args>
|
||||||
|
constexpr Scope<Underlying_T> create_scope(Args &&...args)
|
||||||
|
{
|
||||||
|
return std::make_unique<Underlying_T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts c-style pointer of type `Underlying_T` to a `Scope<Underlying_T>`. */
|
||||||
|
template<typename Underlying_T>
|
||||||
|
constexpr Scope<Underlying_T> make_scope(Underlying_T *raw_pointer)
|
||||||
|
{
|
||||||
|
return Scope<Underlying_T>(raw_pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lt::memory
|
||||||
|
|
@ -33,13 +33,11 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tick() -> bool override
|
void tick(app::TickInfo tick) override
|
||||||
{
|
{
|
||||||
using Surface = lt::surface::SurfaceComponent;
|
using Surface = lt::surface::SurfaceComponent;
|
||||||
using Input = lt::input::InputComponent;
|
using Input = lt::input::InputComponent;
|
||||||
|
|
||||||
static lt::Timer timer;
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds { 10 });
|
std::this_thread::sleep_for(std::chrono::milliseconds { 10 });
|
||||||
auto should_quit = false;
|
auto should_quit = false;
|
||||||
for (auto &[entity, surface, input] : m_registry->view<Surface, Input>())
|
for (auto &[entity, surface, input] : m_registry->view<Surface, Input>())
|
||||||
|
|
@ -78,8 +76,12 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.reset();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
return should_quit;
|
m_last_tick_result = app::TickResult {
|
||||||
|
.info = tick,
|
||||||
|
.duration = now - tick.start_time,
|
||||||
|
.end_time = now,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_register() override
|
void on_register() override
|
||||||
|
|
@ -90,11 +92,19 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||||
|
{
|
||||||
|
return m_last_tick_result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ref<ecs::Registry> m_registry;
|
Ref<ecs::Registry> m_registry;
|
||||||
|
|
||||||
lt::input::InputAction::Key m_quit_action_key;
|
lt::input::InputAction::Key m_quit_action_key;
|
||||||
|
|
||||||
std::array<lt::input::InputAction::Key, 4> m_debug_action_keys {};
|
std::array<lt::input::InputAction::Key, 4> m_debug_action_keys {};
|
||||||
|
|
||||||
|
app::TickResult m_last_tick_result {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mirror: public app::Application
|
class Mirror: public app::Application
|
||||||
|
|
@ -201,7 +211,7 @@ private:
|
||||||
|
|
||||||
Ref<MirrorSystem> m_mirror_system;
|
Ref<MirrorSystem> m_mirror_system;
|
||||||
|
|
||||||
lt::ecs::Entity m_window = lt::ecs::null_entity;
|
lt::ecs::EntityId m_window = lt::ecs::null_entity;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto app::create_application() -> Scope<app::Application>
|
auto app::create_application() -> Scope<app::Application>
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,17 @@
|
||||||
add_library_module(renderer
|
add_library_module(renderer
|
||||||
system.cpp
|
system.cpp
|
||||||
blender.cpp
|
vk/context.cpp
|
||||||
buffers.cpp
|
|
||||||
framebuffer.cpp
|
|
||||||
graphics_context.cpp
|
|
||||||
render_command.cpp
|
|
||||||
renderer.cpp
|
|
||||||
shader.cpp
|
|
||||||
texture.cpp
|
|
||||||
vertex_layout.cpp
|
|
||||||
programs/quad.cpp
|
|
||||||
programs/texture.cpp
|
|
||||||
programs/tinted_texture.cpp
|
|
||||||
gl/blender.cpp
|
|
||||||
gl/buffers.cpp
|
|
||||||
gl/framebuffers.cpp
|
|
||||||
gl/graphics_context.cpp
|
|
||||||
gl/render_command.cpp
|
|
||||||
gl/shader.cpp
|
|
||||||
gl/texture.cpp
|
|
||||||
gl/vertex_layout.cpp
|
|
||||||
vk/instance.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(renderer
|
||||||
renderer
|
PUBLIC
|
||||||
PUBLIC camera
|
app
|
||||||
PUBLIC input
|
ecs
|
||||||
PUBLIC logger
|
vulkan
|
||||||
PUBLIC imgui
|
memory
|
||||||
PUBLIC asset_parser
|
surface
|
||||||
PRIVATE lt_debug
|
|
||||||
PRIVATE window
|
|
||||||
PUBLIC vulkan
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test_module(renderer
|
add_test_module(renderer
|
||||||
system.test.cpp
|
system.test.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(
|
|
||||||
renderer_tests
|
|
||||||
PRIVATE lt_debug
|
|
||||||
PRIVATE window
|
|
||||||
)
|
|
||||||
|
|
|
||||||
17
modules/renderer/legacy/private/system.cpp
Normal file
17
modules/renderer/legacy/private/system.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#include <lt_debug/assertions.hpp>
|
||||||
|
#include <renderer/system.hpp>
|
||||||
|
|
||||||
|
namespace lt::renderer {
|
||||||
|
|
||||||
|
System::System(InitRequirements requirements): m_registry(std::move(requirements.registry))
|
||||||
|
{
|
||||||
|
ensure(m_registry, "null registry");
|
||||||
|
}
|
||||||
|
|
||||||
|
System::~System() = default;
|
||||||
|
|
||||||
|
void System::tick(TickRequirements requirements)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lt::renderer
|
||||||
44
modules/renderer/legacy/private/system.test.cpp
Normal file
44
modules/renderer/legacy/private/system.test.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include <ranges>
|
||||||
|
#include <renderer/system.hpp>
|
||||||
|
#include <test/test.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
|
||||||
|
using namespace lt;
|
||||||
|
|
||||||
|
using lt::test::Case;
|
||||||
|
using lt::test::Suite;
|
||||||
|
|
||||||
|
Suite raii = [] {
|
||||||
|
using lt::test::expect_true;
|
||||||
|
using lt::test::expect_throw;
|
||||||
|
using renderer::System;
|
||||||
|
|
||||||
|
Case { "happy" } = [=] {
|
||||||
|
std::ignore = System { {
|
||||||
|
.registry = create_ref<ecs::Registry>(),
|
||||||
|
} };
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "unhappy" } = [=] {
|
||||||
|
expect_throw([=] {
|
||||||
|
std::ignore = System { {
|
||||||
|
.registry = {},
|
||||||
|
} };
|
||||||
|
});
|
||||||
|
|
||||||
|
expect_throw([=] {
|
||||||
|
std::ignore = System { {
|
||||||
|
.registry = create_ref<ecs::Registry>(),
|
||||||
|
} };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "plenty" } = [=] {
|
||||||
|
for (auto idx : std::views::iota(0, 100'001))
|
||||||
|
{
|
||||||
|
std::ignore = System { {
|
||||||
|
.registry = create_ref<ecs::Registry>(),
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
56
modules/renderer/legacy/public/system.hpp
Normal file
56
modules/renderer/legacy/public/system.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <base/base.hpp>
|
||||||
|
#include <ecs/registry.hpp>
|
||||||
|
|
||||||
|
namespace lt::renderer {
|
||||||
|
|
||||||
|
/** The system for putting gore on your display
|
||||||
|
*
|
||||||
|
* Exclusively operates on components:
|
||||||
|
* - RendererComponent
|
||||||
|
* - PostEffectsComponent
|
||||||
|
* - UserInterfaceComponent
|
||||||
|
*
|
||||||
|
* Requires read acces on components:
|
||||||
|
* - TransformComponent
|
||||||
|
*/
|
||||||
|
class System
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** The configurations of this system. */
|
||||||
|
struct Properties
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The requirements for this system to initialize. */
|
||||||
|
struct InitRequirements
|
||||||
|
{
|
||||||
|
Ref<ecs::Registry> registry;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The requirements for this system to tick. */
|
||||||
|
struct TickRequirements
|
||||||
|
{
|
||||||
|
double delta_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] System(InitRequirements requirements);
|
||||||
|
|
||||||
|
System(System &&) = default;
|
||||||
|
|
||||||
|
System(const System &) = delete;
|
||||||
|
|
||||||
|
auto operator=(System &&) -> System & = default;
|
||||||
|
|
||||||
|
auto operator=(const System &) -> System & = delete;
|
||||||
|
|
||||||
|
~System();
|
||||||
|
|
||||||
|
void tick(TickRequirements requirements);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ref<ecs::Registry> m_registry;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::renderer
|
||||||
|
|
@ -1,17 +1,5 @@
|
||||||
#include <lt_debug/assertions.hpp>
|
|
||||||
#include <renderer/system.hpp>
|
#include <renderer/system.hpp>
|
||||||
|
|
||||||
namespace lt::renderer {
|
namespace lt::renderer {
|
||||||
|
|
||||||
System::System(InitRequirements requirements): m_registry(std::move(requirements.registry))
|
|
||||||
{
|
|
||||||
ensure(m_registry, "null registry");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
System::~System() = default;
|
|
||||||
|
|
||||||
void System::tick(TickRequirements requirements)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lt::renderer
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,123 @@
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
#include <renderer/system.hpp>
|
#include <renderer/system.hpp>
|
||||||
|
#include <surface/system.hpp>
|
||||||
#include <test/test.hpp>
|
#include <test/test.hpp>
|
||||||
#include <window/window.hpp>
|
|
||||||
|
|
||||||
using namespace lt;
|
using namespace lt;
|
||||||
|
using std::ignore;
|
||||||
|
using test::Case;
|
||||||
|
using test::expect_eq;
|
||||||
|
using test::expect_ne;
|
||||||
|
using test::expect_not_nullptr;
|
||||||
|
using test::expect_throw;
|
||||||
|
using test::expect_true;
|
||||||
|
using test::Suite;
|
||||||
|
|
||||||
using lt::test::Case;
|
using renderer::System;
|
||||||
using lt::test::Suite;
|
|
||||||
|
constexpr auto resolution = math::uvec2 { 800, 600 };
|
||||||
|
|
||||||
|
struct SurfaceContext
|
||||||
|
{
|
||||||
|
surface::System system;
|
||||||
|
ecs::Entity entity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RendererContext
|
||||||
|
{
|
||||||
|
Ref<ecs::Registry> registry;
|
||||||
|
System system;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto create_surface() -> SurfaceContext
|
||||||
|
{
|
||||||
|
using surface::SurfaceComponent;
|
||||||
|
|
||||||
|
auto surface_registry = create_ref<ecs::Registry>();
|
||||||
|
auto surface_entity = surface_registry->create_entity();
|
||||||
|
auto surface_system = surface::System(surface_registry);
|
||||||
|
surface_registry->add<SurfaceComponent>(
|
||||||
|
surface_entity,
|
||||||
|
SurfaceComponent::CreateInfo {
|
||||||
|
.title = "",
|
||||||
|
.resolution = resolution,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
.system = std::move(surface_system),
|
||||||
|
.entity = ecs::Entity { surface_registry, surface_entity },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto create_system() -> std::pair<RendererContext, SurfaceContext>
|
||||||
|
{
|
||||||
|
auto surface_context = create_surface();
|
||||||
|
auto &[surface_system, surface_entity] = surface_context;
|
||||||
|
auto registry = create_ref<ecs::Registry>();
|
||||||
|
auto stats = create_ref<app::SystemStats>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
RendererContext {
|
||||||
|
.registry = registry,
|
||||||
|
.system = System(
|
||||||
|
{
|
||||||
|
.registry = registry,
|
||||||
|
.surface_entity = surface_entity,
|
||||||
|
.system_stats = stats,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
std::move(surface_context),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Suite raii = [] {
|
Suite raii = [] {
|
||||||
using lt::test::expect_true;
|
Case { "happy path won't throw" } = [&] {
|
||||||
using lt::test::expect_throw;
|
std::ignore = create_system();
|
||||||
using renderer::System;
|
|
||||||
|
|
||||||
Case { "happy" } = [=] {
|
|
||||||
std::ignore = System { {
|
|
||||||
.registry = create_ref<ecs::Registry>(),
|
|
||||||
} };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "unhappy" } = [=] {
|
Case { "happy path has no validation errors" } = [&] {
|
||||||
expect_throw([=] {
|
auto [renderer, surface] = create_system();
|
||||||
std::ignore = System { {
|
expect_true(renderer.system.get_stats().empty_diagnosis());
|
||||||
.registry = {},
|
|
||||||
} };
|
|
||||||
});
|
|
||||||
|
|
||||||
expect_throw([=] {
|
|
||||||
std::ignore = System { {
|
|
||||||
.registry = create_ref<ecs::Registry>(),
|
|
||||||
} };
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "plenty" } = [=] {
|
Case { "unhappy path throws" } = [] {
|
||||||
for (auto idx : std::views::iota(0, 100'001))
|
auto [surface_system, surface_entity] = create_surface();
|
||||||
{
|
auto empty_registry = create_ref<ecs::Registry>();
|
||||||
std::ignore = System { {
|
auto empty_entity = ecs::Entity { empty_registry, empty_registry->create_entity() };
|
||||||
.registry = create_ref<ecs::Registry>(),
|
auto registry = create_ref<ecs::Registry>();
|
||||||
} };
|
auto stats = create_ref<app::SystemStats>();
|
||||||
}
|
|
||||||
|
expect_throw([&] {
|
||||||
|
std::ignore = System(
|
||||||
|
{
|
||||||
|
.registry = {},
|
||||||
|
.surface_entity = surface_entity,
|
||||||
|
.system_stats = stats,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect_throw([&] {
|
||||||
|
std::ignore = System(
|
||||||
|
System::CreateInfo {
|
||||||
|
.registry = surface_entity.get_registry(),
|
||||||
|
.surface_entity = empty_entity,
|
||||||
|
.system_stats = stats,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect_throw([&] {
|
||||||
|
std::ignore = System(
|
||||||
|
System::CreateInfo {
|
||||||
|
.registry = surface_entity.get_registry(),
|
||||||
|
.surface_entity = surface_entity,
|
||||||
|
.system_stats = {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <renderer/vk/backend.hpp>
|
#include <renderer/vk/context.hpp>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
|
|
@ -83,10 +83,16 @@ PFN_vkCmdBindPipeline vk_cmd_bind_pipeline;
|
||||||
PFN_vkCmdDraw vk_cmd_draw;
|
PFN_vkCmdDraw vk_cmd_draw;
|
||||||
PFN_vkCmdSetViewport vk_cmd_set_viewport;
|
PFN_vkCmdSetViewport vk_cmd_set_viewport;
|
||||||
PFN_vkCmdSetScissor vk_cmd_set_scissors;
|
PFN_vkCmdSetScissor vk_cmd_set_scissors;
|
||||||
|
|
||||||
|
PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr;
|
||||||
|
PFN_vkDestroySurfaceKHR vk_destroy_surface_khr;
|
||||||
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
|
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
Backend::Backend()
|
Context::Context(const ecs::Entity &surface_entity, Ref<app::SystemStats> system_stats)
|
||||||
|
: m_stats(std::move(system_stats))
|
||||||
{
|
{
|
||||||
|
ensure(m_stats, "Failed to create Vulkan Context: null stats");
|
||||||
|
|
||||||
load_library();
|
load_library();
|
||||||
load_global_functions();
|
load_global_functions();
|
||||||
|
|
||||||
|
|
@ -100,16 +106,31 @@ Backend::Backend()
|
||||||
load_device_functions();
|
load_device_functions();
|
||||||
|
|
||||||
initialize_queue();
|
initialize_queue();
|
||||||
|
|
||||||
|
const auto &component = surface_entity.get<surface::SurfaceComponent>();
|
||||||
|
|
||||||
|
auto xlib_surface_create_info = VkXlibSurfaceCreateInfoKHR {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||||
|
.dpy = component.get_native_data().display,
|
||||||
|
.window = component.get_native_data().window,
|
||||||
|
};
|
||||||
|
|
||||||
|
vk_create_xlib_surface_khr(m_instance, &xlib_surface_create_info, nullptr, &m_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
Backend::~Backend()
|
Context::~Context()
|
||||||
{
|
{
|
||||||
vk_destroy_device(m_device, nullptr);
|
vk_destroy_device(m_device, nullptr);
|
||||||
vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr);
|
|
||||||
|
if (m_instance)
|
||||||
|
{
|
||||||
|
vk_destroy_surface_khr(m_instance, m_surface, nullptr);
|
||||||
|
vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
vk_destroy_instance(m_instance, nullptr);
|
vk_destroy_instance(m_instance, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const char *
|
auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const char *
|
||||||
{
|
{
|
||||||
if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
|
if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
|
||||||
|
|
@ -132,14 +153,17 @@ auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const
|
||||||
return "PERFORMANCE";
|
return "PERFORMANCE";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto parse_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity) -> LogLvl
|
auto parse_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity)
|
||||||
|
-> app::SystemDiagnosis::Severity
|
||||||
{
|
{
|
||||||
|
using enum app::SystemDiagnosis::Severity;
|
||||||
|
|
||||||
switch (message_severity)
|
switch (message_severity)
|
||||||
{
|
{
|
||||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: return LogLvl::trace;
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: return verbose;
|
||||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: return LogLvl::info;
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: return info;
|
||||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: return LogLvl::warn;
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: return warning;
|
||||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: return LogLvl::error;
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: return error;
|
||||||
default: ensure(false, "Invalid message severity: {}", static_cast<int>(message_severity));
|
default: ensure(false, "Invalid message severity: {}", static_cast<int>(message_severity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,16 +177,34 @@ auto validation_layers_callback(
|
||||||
void *const vulkan_user_data
|
void *const vulkan_user_data
|
||||||
) -> VkBool32
|
) -> VkBool32
|
||||||
{
|
{
|
||||||
std::ignore = vulkan_user_data;
|
auto stats = *(Ref<app::SystemStats> *)vulkan_user_data; // NOLINT
|
||||||
|
|
||||||
const auto &type = parse_message_type(message_types);
|
const auto &type = parse_message_type(message_types);
|
||||||
const auto level = parse_message_severity(message_severity);
|
|
||||||
|
|
||||||
Logger::log(level, ":: <{}> :: {}", type, callback_data->pMessage);
|
auto message = std::format(
|
||||||
|
"Vulkan Validation Message:\ntype: {}\nseverity: {}\nmessage: {}",
|
||||||
|
type,
|
||||||
|
std::to_underlying(parse_message_severity(message_severity)),
|
||||||
|
callback_data->pMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
auto severity = parse_message_severity(message_severity);
|
||||||
|
if (std::to_underlying(severity) < 2)
|
||||||
|
{
|
||||||
|
return static_cast<VkBool32>(VK_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
stats->push_diagnosis(
|
||||||
|
app::SystemDiagnosis {
|
||||||
|
.message = message,
|
||||||
|
.code = {}, // TODO(Light): extract vulkan validation-layers code from the message
|
||||||
|
.severity = severity,
|
||||||
|
}
|
||||||
|
);
|
||||||
return static_cast<VkBool32>(VK_FALSE);
|
return static_cast<VkBool32>(VK_FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::initialize_instance()
|
void Context::initialize_instance()
|
||||||
{
|
{
|
||||||
auto app_info = VkApplicationInfo {
|
auto app_info = VkApplicationInfo {
|
||||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||||
|
|
@ -198,10 +240,10 @@ void Backend::initialize_instance()
|
||||||
auto extensions = std::vector<VkExtensionProperties>(count);
|
auto extensions = std::vector<VkExtensionProperties>(count);
|
||||||
vk_enumerate_instance_extension_properties(nullptr, &count, extensions.data());
|
vk_enumerate_instance_extension_properties(nullptr, &count, extensions.data());
|
||||||
|
|
||||||
log_inf("Available vulkan instance extensions:");
|
// log_inf("Available vulkan instance extensions:");
|
||||||
for (auto &ext : extensions)
|
for (auto &ext : extensions)
|
||||||
{
|
{
|
||||||
log_inf("\t{} @ {}", ext.extensionName, ext.specVersion);
|
// log_inf("\t{} @ {}", ext.extensionName, ext.specVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,7 +252,7 @@ void Backend::initialize_instance()
|
||||||
ensure(m_instance, "Failed to create vulkan instance");
|
ensure(m_instance, "Failed to create vulkan instance");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::initialize_debug_messenger()
|
void Context::initialize_debug_messenger()
|
||||||
{
|
{
|
||||||
const auto info = VkDebugUtilsMessengerCreateInfoEXT {
|
const auto info = VkDebugUtilsMessengerCreateInfoEXT {
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||||
|
|
@ -226,6 +268,8 @@ void Backend::initialize_debug_messenger()
|
||||||
|
|
||||||
|
|
||||||
.pfnUserCallback = &validation_layers_callback,
|
.pfnUserCallback = &validation_layers_callback,
|
||||||
|
|
||||||
|
.pUserData = &m_stats,
|
||||||
};
|
};
|
||||||
|
|
||||||
ensure(
|
ensure(
|
||||||
|
|
@ -234,7 +278,7 @@ void Backend::initialize_debug_messenger()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::initialize_physical_device()
|
void Context::initialize_physical_device()
|
||||||
{
|
{
|
||||||
auto count = 0u;
|
auto count = 0u;
|
||||||
vk_enumerate_physical_devices(m_instance, &count, nullptr);
|
vk_enumerate_physical_devices(m_instance, &count, nullptr);
|
||||||
|
|
@ -260,7 +304,7 @@ void Backend::initialize_physical_device()
|
||||||
ensure(m_physical_device, "Failed to find any suitable Vulkan physical device");
|
ensure(m_physical_device, "Failed to find any suitable Vulkan physical device");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::initialize_logical_device()
|
void Context::initialize_logical_device()
|
||||||
{
|
{
|
||||||
const float priorities = .0f;
|
const float priorities = .0f;
|
||||||
|
|
||||||
|
|
@ -292,12 +336,12 @@ void Backend::initialize_logical_device()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::initialize_queue()
|
void Context::initialize_queue()
|
||||||
{
|
{
|
||||||
vk_get_device_queue(m_device, find_suitable_queue_family(), 0, &m_queue);
|
vk_get_device_queue(m_device, find_suitable_queue_family(), 0, &m_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::load_library()
|
void Context::load_library()
|
||||||
{
|
{
|
||||||
library = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
library = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
||||||
ensure(library, "Failed to dlopen libvulkan.so");
|
ensure(library, "Failed to dlopen libvulkan.so");
|
||||||
|
|
@ -309,13 +353,13 @@ void Backend::load_library()
|
||||||
ensure(vk_get_instance_proc_address, "Failed to load vulkan function: vkGetInstanceProcAddr");
|
ensure(vk_get_instance_proc_address, "Failed to load vulkan function: vkGetInstanceProcAddr");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::load_global_functions()
|
void Context::load_global_functions()
|
||||||
{
|
{
|
||||||
constexpr auto load_fn = []<typename T>(T &pfn, const char *fn_name) {
|
constexpr auto load_fn = []<typename T>(T &pfn, const char *fn_name) {
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
pfn = reinterpret_cast<T>(vk_get_instance_proc_address(nullptr, fn_name));
|
pfn = reinterpret_cast<T>(vk_get_instance_proc_address(nullptr, fn_name));
|
||||||
ensure(pfn, "Failed to load vulkan global function: {}", fn_name);
|
ensure(pfn, "Failed to load vulkan global function: {}", fn_name);
|
||||||
log_trc("Loaded global function: {}", fn_name);
|
// log_trc("Loaded global function: {}", fn_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
load_fn(vk_create_instance, "vkCreateInstance");
|
load_fn(vk_create_instance, "vkCreateInstance");
|
||||||
|
|
@ -323,13 +367,13 @@ void Backend::load_global_functions()
|
||||||
load_fn(vk_enumerate_instance_layer_properties, "vkEnumerateInstanceLayerProperties");
|
load_fn(vk_enumerate_instance_layer_properties, "vkEnumerateInstanceLayerProperties");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::load_instance_functions()
|
void Context::load_instance_functions()
|
||||||
{
|
{
|
||||||
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
|
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
pfn = reinterpret_cast<T>(vk_get_instance_proc_address(m_instance, fn_name));
|
pfn = reinterpret_cast<T>(vk_get_instance_proc_address(m_instance, fn_name));
|
||||||
ensure(pfn, "Failed to load vulkan instance function: {}", fn_name);
|
ensure(pfn, "Failed to load vulkan instance function: {}", fn_name);
|
||||||
log_trc("Loaded instance function: {}", fn_name);
|
// log_trc("Loaded instance function: {}", fn_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
load_fn(vk_destroy_instance, "vkDestroyInstance");
|
load_fn(vk_destroy_instance, "vkDestroyInstance");
|
||||||
|
|
@ -356,15 +400,18 @@ void Backend::load_instance_functions()
|
||||||
load_fn(vk_set_debug_object_name, "vkSetDebugUtilsObjectNameEXT");
|
load_fn(vk_set_debug_object_name, "vkSetDebugUtilsObjectNameEXT");
|
||||||
load_fn(vk_set_debug_object_tag, "vkSetDebugUtilsObjectTagEXT");
|
load_fn(vk_set_debug_object_tag, "vkSetDebugUtilsObjectTagEXT");
|
||||||
load_fn(vk_submit_debug_message, "vkSubmitDebugUtilsMessageEXT");
|
load_fn(vk_submit_debug_message, "vkSubmitDebugUtilsMessageEXT");
|
||||||
|
|
||||||
|
load_fn(vk_create_xlib_surface_khr, "vkCreateXlibSurfaceKHR");
|
||||||
|
load_fn(vk_destroy_surface_khr, "vkDestroySurfaceKHR");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Backend::load_device_functions()
|
void Context::load_device_functions()
|
||||||
{
|
{
|
||||||
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
|
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
pfn = reinterpret_cast<T>(vk_get_device_proc_address(m_device, fn_name));
|
pfn = reinterpret_cast<T>(vk_get_device_proc_address(m_device, fn_name));
|
||||||
ensure(pfn, "Failed to load vulkan device function: {}", fn_name);
|
ensure(pfn, "Failed to load vulkan device function: {}", fn_name);
|
||||||
log_trc("Loaded device function: {}", fn_name);
|
// log_trc("Loaded device function: {}", fn_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
load_fn(vk_get_device_queue, "vkGetDeviceQueue");
|
load_fn(vk_get_device_queue, "vkGetDeviceQueue");
|
||||||
|
|
@ -409,7 +456,7 @@ void Backend::load_device_functions()
|
||||||
load_fn(vk_cmd_set_scissors, "vkCmdSetScissor");
|
load_fn(vk_cmd_set_scissors, "vkCmdSetScissor");
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto Backend::find_suitable_queue_family() const -> uint32_t
|
[[nodiscard]] auto Context::find_suitable_queue_family() const -> uint32_t
|
||||||
{
|
{
|
||||||
auto count = 0u;
|
auto count = 0u;
|
||||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr);
|
vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr);
|
||||||
|
|
@ -1,11 +1,20 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VK_NO_PROTOTYPES
|
#define VK_NO_PROTOTYPES
|
||||||
#include <renderer/backend.hpp>
|
#define VK_USE_PLATFORM_XLIB_KHR
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vulkan/vulkan_xlib.h>
|
||||||
|
|
||||||
|
//
|
||||||
|
#include <app/system.hpp>
|
||||||
|
#include <ecs/entity.hpp>
|
||||||
|
#include <memory/pointer_types/null_on_move.hpp>
|
||||||
|
#include <surface/components.hpp>
|
||||||
|
|
||||||
namespace lt::renderer::vk {
|
namespace lt::renderer::vk {
|
||||||
|
|
||||||
|
using memory::NullOnMove;
|
||||||
|
|
||||||
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
|
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
// global functions
|
// global functions
|
||||||
extern PFN_vkGetInstanceProcAddr vk_get_instance_proc_address;
|
extern PFN_vkGetInstanceProcAddr vk_get_instance_proc_address;
|
||||||
|
|
@ -78,26 +87,54 @@ extern PFN_vkCmdBindPipeline vk_cmd_bind_pipeline;
|
||||||
extern PFN_vkCmdDraw vk_cmd_draw;
|
extern PFN_vkCmdDraw vk_cmd_draw;
|
||||||
extern PFN_vkCmdSetViewport vk_cmd_set_viewport;
|
extern PFN_vkCmdSetViewport vk_cmd_set_viewport;
|
||||||
extern PFN_vkCmdSetScissor vk_cmd_set_scissors;
|
extern PFN_vkCmdSetScissor vk_cmd_set_scissors;
|
||||||
|
|
||||||
|
extern PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr;
|
||||||
|
extern PFN_vkDestroySurfaceKHR vk_destroy_surface_khr;
|
||||||
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
|
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
class Backend: public ::lt::renderer::Backend
|
class Context
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Backend();
|
Context(const ecs::Entity &surface_entity, Ref<app::SystemStats> system_stats);
|
||||||
|
|
||||||
Backend(Backend &&) = default;
|
~Context();
|
||||||
|
|
||||||
auto operator=(Backend &&) -> Backend & = default;
|
Context(Context &&other) noexcept = default;
|
||||||
|
|
||||||
Backend(const Backend &) = delete;
|
auto operator=(Context &&other) noexcept -> Context & = default;
|
||||||
|
|
||||||
auto operator=(const Backend &) -> Backend & = delete;
|
Context(const Context &) = delete;
|
||||||
|
|
||||||
~Backend() override;
|
auto operator=(const Context &) -> Context & = delete;
|
||||||
|
|
||||||
[[nodiscard]] constexpr auto get_api() const -> API override
|
[[nodiscard]] auto instance() -> VkInstance
|
||||||
{
|
{
|
||||||
return API::vulkan;
|
return m_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto physical_device() -> VkPhysicalDevice
|
||||||
|
{
|
||||||
|
return m_physical_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto device() -> VkDevice
|
||||||
|
{
|
||||||
|
return m_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto queue() -> VkQueue
|
||||||
|
{
|
||||||
|
return m_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto debug_messenger() -> VkDebugUtilsMessengerEXT
|
||||||
|
{
|
||||||
|
return m_debug_messenger;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_stats() const -> const app::SystemStats &
|
||||||
|
{
|
||||||
|
return *m_stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -121,15 +158,23 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] auto find_suitable_queue_family() const -> uint32_t;
|
[[nodiscard]] auto find_suitable_queue_family() const -> uint32_t;
|
||||||
|
|
||||||
VkInstance m_instance {};
|
Ref<ecs::Registry> m_registry;
|
||||||
|
|
||||||
VkPhysicalDevice m_physical_device {};
|
NullOnMove<VkInstance> m_instance = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkDevice m_device {};
|
NullOnMove<VkPhysicalDevice> m_physical_device = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkQueue m_queue {};
|
NullOnMove<VkDevice> m_device = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkDebugUtilsMessengerEXT m_debug_messenger {};
|
NullOnMove<VkQueue> m_queue = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
NullOnMove<VkSwapchainKHR> m_swapcha = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
NullOnMove<VkDebugUtilsMessengerEXT> m_debug_messenger = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
NullOnMove<VkSurfaceKHR> m_surface = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
Ref<app::SystemStats> m_stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lt::renderer::vk
|
} // namespace lt::renderer::vk
|
||||||
26
modules/renderer/private/vk/surface.hpp
Normal file
26
modules/renderer/private/vk/surface.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define VK_NO_PROTOTYPES
|
||||||
|
#define VK_USE_PLATFORM_XLIB_KHR
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vulkan/vulkan_xlib.h>
|
||||||
|
|
||||||
|
//
|
||||||
|
#include <ecs/entity.hpp>
|
||||||
|
|
||||||
|
namespace lt::renderer::vk {
|
||||||
|
|
||||||
|
class Surface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Surface(ecs::Entity entity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~Surface();
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::renderer::vk
|
||||||
22
modules/renderer/private/vk/swapchain.hpp
Normal file
22
modules/renderer/private/vk/swapchain.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define VK_NO_PROTOTYPES
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
namespace lt::renderer::vk {
|
||||||
|
|
||||||
|
class Swapchain
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Swapchain()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkSwapchainKHR m_swapchain {};
|
||||||
|
|
||||||
|
std::vector<VkImage> images;
|
||||||
|
std::vector<VkImageView> image_views;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::renderer::vk
|
||||||
|
|
@ -1,41 +1,30 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <base/base.hpp>
|
#include <app/system.hpp>
|
||||||
#include <ecs/registry.hpp>
|
#include <ecs/entity.hpp>
|
||||||
|
#include <renderer/validation.hpp>
|
||||||
|
#include <renderer/vk/context.hpp>
|
||||||
|
|
||||||
namespace lt::renderer {
|
namespace lt::renderer {
|
||||||
|
|
||||||
/** The system for putting gore on your display
|
class System: app::ISystem
|
||||||
*
|
|
||||||
* Exclusively operates on components:
|
|
||||||
* - RendererComponent
|
|
||||||
* - PostEffectsComponent
|
|
||||||
* - UserInterfaceComponent
|
|
||||||
*
|
|
||||||
* Requires read acces on components:
|
|
||||||
* - TransformComponent
|
|
||||||
*/
|
|
||||||
class System
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/** The configurations of this system. */
|
struct CreateInfo
|
||||||
struct Properties
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
/** The requirements for this system to initialize. */
|
|
||||||
struct InitRequirements
|
|
||||||
{
|
{
|
||||||
Ref<ecs::Registry> registry;
|
Ref<ecs::Registry> registry;
|
||||||
|
ecs::Entity surface_entity;
|
||||||
|
Ref<app::SystemStats> system_stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** The requirements for this system to tick. */
|
[[nodiscard]] System(CreateInfo info)
|
||||||
struct TickRequirements
|
: m_registry(std::move(info.registry))
|
||||||
|
, m_context(info.surface_entity, std::move(info.system_stats))
|
||||||
{
|
{
|
||||||
double delta_time;
|
ensure(m_registry, "Failed to initialize renderer system: null registry");
|
||||||
};
|
}
|
||||||
|
|
||||||
[[nodiscard]] System(InitRequirements requirements);
|
~System() override = default;
|
||||||
|
|
||||||
System(System &&) = default;
|
System(System &&) = default;
|
||||||
|
|
||||||
|
|
@ -45,12 +34,38 @@ public:
|
||||||
|
|
||||||
auto operator=(const System &) -> System & = delete;
|
auto operator=(const System &) -> System & = delete;
|
||||||
|
|
||||||
~System();
|
void on_register() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void tick(TickRequirements requirements);
|
void on_unregister() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_validation_state();
|
||||||
|
|
||||||
|
void tick(app::TickInfo tick) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_stats() const -> const app::SystemStats &
|
||||||
|
{
|
||||||
|
return m_context.get_stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||||
|
{
|
||||||
|
return m_last_tick_result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ref<ecs::Registry> m_registry;
|
Ref<ecs::Registry> m_registry;
|
||||||
|
|
||||||
|
renderer::Validation m_validation;
|
||||||
|
|
||||||
|
vk::Context m_context;
|
||||||
|
|
||||||
|
app::TickResult m_last_tick_result {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lt::renderer
|
} // namespace lt::renderer
|
||||||
|
|
|
||||||
25
modules/renderer/public/validation.hpp
Normal file
25
modules/renderer/public/validation.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace lt::renderer {
|
||||||
|
|
||||||
|
enum class Severity : uint8_t
|
||||||
|
{
|
||||||
|
off,
|
||||||
|
|
||||||
|
very_verbose,
|
||||||
|
verbose,
|
||||||
|
info,
|
||||||
|
warning,
|
||||||
|
error,
|
||||||
|
critical,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Validation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void push_diagnosis();
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::renderer
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue