diff --git a/.clang-tidy b/.clang-tidy index e50bece..4bc6fa4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -50,6 +50,8 @@ CheckOptions: value: true - key: cppcoreguidelines-rvalue-reference-param-not-moved.IgnoreUnnamedParams value: true + - key: readability-convert-member-functions-to-static + value: false - key: modernize-use-auto.MinTypeNameLength value: 5 diff --git a/.gitignore b/.gitignore index 41153d3..4d2b7e8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ compile_commands.json .cache logs/ CMakeFiles/ - +latex/ # macOS .DS_Store @@ -13,3 +13,5 @@ vcpkg/buildtrees/ vcpkg/downloads/ vcpkg/packages/ vcpkg/installed/ +.idea +\html/ diff --git a/docs/rfc/RFC-0001-opaque-generational-handles.md b/docs/rfc/RFC-0001-opaque-generational-handles.md new file mode 100644 index 0000000..b19dd54 --- /dev/null +++ b/docs/rfc/RFC-0001-opaque-generational-handles.md @@ -0,0 +1,221 @@ +# RFC: Opaque Generational Handles + +**Status:** Draft +**Author:** gituser12981u2 +**Target:** Internal Engine Architecture +**Date:** March, 2026 +**Scope**: Opaque handle types used for resource addressing +and lifetime + +## 1. Abstract + +This document defines the structure and requirements for opaque +generational handle types passed out by registry systems. These handles +provide a stable, type safe mechanism for referencing registry managed +resources. + +## 2. Motivation + +Engine systems require a mechanism to reference resources without: + +- Exposing raw backend handles +- Allowing accidental destruction or mutation +- Introducing aliasing or lifetime ambiguity + +Opaque generational handles address these concerns by: + +- Decoupling identity from storage +- Enabling validation via generation counters +- Supporting efficient slot reuse without dangling references + +A generic handle type: + +- Eliminates repetitive boilerplate across handle definitions +- Ensures uniform behavior across all handle types +- Prevents accidental mixing of handles from different domains +via type tagging + +## 3. Design Principles + +### 3.1 Opaque ownership boundary + +Handles do not grant ownership or destruction rights over the +underlying resource. + +### 3.2 Value Semantics + +Handle behave as lightweight, copyable value types. + +### 3.3 Stale reference detection + +- Handles must support detection of invalid or outdated references via +generation tracking. + +### 3.4 Minimal structure + +The handle representation is intentionally minimal to reduce overhead +and maximize performance. + +## 4. Specification + +### 4.1 OpaqueHandle + +#### 4.1.1 Definition + +A type satisfying OpaqueHandle: + +- Encodes an index into a storage domain (e.g., registry slot array) +- Encodes a generation counter associated with that index +- Supports value semantics +- Is equality comparable + +#### 4.1.2 Requirements + +An opaque handle type shall: + +- Expose an index member convertible to uint32_t +- Expose a generation member convertible to uint32_t +- Satisfy std::equality_comparable + +#### 4.1.3 Specification + +```cpp +template +concept OpaqueHandle = + std::equality_comparable && + requires(H handle) { + { handle.index } -> std::convertible_to; + { handle.generation } -> std::convertible_to; + }; +``` + +### 4.2 Generic Handle Type + +#### 4.2.1 Definition + +The engine shall provide a generic handle template parameterized by a tag type: + +```cpp +template +struct GenericHandle final { + uint32_t index = 0; + uint32_t generation = 0; + + friend constexpr bool operator==(GenericHandle, GenericHandle) noexcept = default; +} +``` + +#### 4.2.2 Requirements + +`GenericHandle`: + +- Satisfies `OpaqueHandle` +- Is a distinct type for each unique `Tag` +- Has no implicit conversions between different tag instantiations + +#### 4.2.3 Tag Types + +Tag types are empty types used solely to distinguish handle domains. + +Example: + +```cpp +struct FrameHandleTag; +struct InstanceHandleTag; + +using FrameHandle = GenericHandle; +using InstanceHandle = GenericHandle; +``` + +### 4.3 Registry Storage and Allocation + +#### 4.3.1 Definition + +Registries using opaque generational handles may allocate slot storage and +free lists from a caller supplied allocator or memory resource. + +#### 4.3.2 Requirements + +A registry implementation should: + +- Default to a system allocator when no allocator is supplied +- Allow short lived backing storage such as a bump allocator for +temporary registries +- Preserve handle generation semantics regardless of allocator choice + +#### 4.3.3 Rationale + +Handle identity and stale reference detection are orthogonal to the +allocation strategy used by the registry itself. Allowing allocator +injection makes the same registry design suitable for: + +- Long lived systems using the system allocator +- Frame or scratch scoped registries backed by a bump allocator +- Tooling or tests that want explicit control over transient memory + +#### 4.2.3 Rationale + +The generic handle pattern ensures: + +- Compile-time separation between resource domains +- Elimination of repetitive handle definitions +- Consistent structure and behavior across all handles + +## 5. Semantics + +### 5.1 Identity + +A handle uniquely identifies a resource within a specific storage +domain by the pair: + +```cpp +(index, generation) +``` + +Two handles are equal if and only if both components are equal. + +### 5.2 Validity + +A handle is considered valid with respect to a registry if: + +- index refers to an in-bounds slot, and +- generation matches the current generation of that slot, and +- the slot is marked live + +Validity is encoded in the handle itself and must be checked +by the owning system. + +### 5.3 Staleness + +A handle becomes stale when: + +- The referenced slot is destroyed, and +- The slot's generation is incremented + +Stale handles must not alias newly created objects occupying +the same index. + +### 5.4 Lifetime + +Handles: + +- Do not own resources +- Do not extend resource lifetime +- May outlive the underlying resource + +Registry storage may itself be temporary when backed by a short term +allocator. Destroying or resetting that storage invalidates the registry +as a whole, but does not change the semantic meaning of handles already +issued: they remain non-owning values and require the owning system for +validation. + +Using a handle after destruction is defined behavior only +insofar as the owning system detects and rejects it. + +## 6. Example + +```cpp +struct FrameHandleTag; + +using FrameHandle = GenericHandle; +``` diff --git a/include/quark/utils/allocator.hpp b/include/quark/utils/allocator.hpp new file mode 100644 index 0000000..c8b80e2 --- /dev/null +++ b/include/quark/utils/allocator.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace util { + +using BumpAllocator = std::pmr::monotonic_buffer_resource; +using MemoryResource = std::pmr::memory_resource; + +[[nodiscard]] inline MemoryResource *default_memory_resource() noexcept { + return std::pmr::get_default_resource(); +} + +[[nodiscard]] inline MemoryResource *system_memory_resource() noexcept { + return std::pmr::new_delete_resource(); +} + +} // namespace util \ No newline at end of file diff --git a/include/quark/utils/diagnostic.hpp b/include/quark/utils/diagnostic.hpp index f09dae8..d95db32 100644 --- a/include/quark/utils/diagnostic.hpp +++ b/include/quark/utils/diagnostic.hpp @@ -14,7 +14,7 @@ using SinkList = std::vector; void set_diagnostic_sinks(std::span sinks) noexcept; std::shared_ptr diagnostic_sinks_snapshot() noexcept; -void report(const DiagnosticEvent &) noexcept; +void report(const DiagnosticEvent & /*e*/) noexcept; template inline void report_if_error(const util::Result &r) noexcept { diff --git a/include/quark/utils/generational_registry.hpp b/include/quark/utils/generational_registry.hpp new file mode 100644 index 0000000..bcc6b1e --- /dev/null +++ b/include/quark/utils/generational_registry.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace util { + +template class GenerationalRegistry final { +public: + using CreateInfo = typename T::CreateInfo; + + explicit GenerationalRegistry( + std::pmr::memory_resource *memory_resource = default_memory_resource()) + : slots_(memory_resource), free_(memory_resource) {} + + ~GenerationalRegistry() = default; + + QUARK_MOVE_ONLY(GenerationalRegistry); + + void clear() noexcept { + for (auto &slot : slots_) { + if (slot.live) { + slot.object.destroy(); + slot.live = false; + ++slot.generation; + } + } + + free_.clear(); + free_.reserve(slots_.size()); + for (uint32_t index = 0; index < slots_.size(); ++index) { + free_.push_back(index); + } + } + + [[nodiscard]] util::Result create(const CreateInfo &create_info) { + uint32_t index = 0; + + if (!free_.empty()) { + index = free_.back(); + free_.pop_back(); + } else { + index = static_cast(slots_.size()); + slots_.emplace_back(); + } + + Slot &slot = slots_[index]; + + if (slot.live) { + slot.object.destroy(); + slot.live = false; + ++slot.generation; + } + + QUARK_TRY_STATUS(slot.object.create(create_info)); + slot.live = true; + + return Handle{.index = index, .generation = slot.generation}; + } + + void destroy(Handle handle) noexcept { + if (!handle.valid() || handle.index >= slots_.size()) { + return; + } + + Slot &slot = slots_[handle.index]; + if (!matches_(handle, slot)) { + return; + } + + slot.object.destroy(); + slot.live = false; + ++slot.generation; + + free_.push_back(handle.index); + } + + [[nodiscard]] bool alive(Handle handle) const noexcept { + if (!handle.valid() || handle.index >= slots_.size()) { + return false; + } + + return matches_(handle, slots_[handle.index]); + } + + T *get(Handle handle) noexcept { + if (!alive(handle)) { + return nullptr; + } + + return &slots_[handle.index].object; + } + + [[nodiscard]] const T *get(Handle handle) const noexcept { + if (!alive(handle)) { + return nullptr; + } + + return &slots_[handle.index].object; + } + +private: + struct Slot { + T object; + uint32_t generation = 1; + bool live = false; + }; + + [[nodiscard]] static bool matches_(Handle handle, const Slot &slot) noexcept { + return handle.valid() && slot.live && slot.generation == handle.generation; + } + + std::pmr::vector slots_; + std::pmr::vector free_; +}; + +} // namespace util \ No newline at end of file diff --git a/include/quark/utils/generic_handle.hpp b/include/quark/utils/generic_handle.hpp new file mode 100644 index 0000000..1b67e82 --- /dev/null +++ b/include/quark/utils/generic_handle.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +namespace util { + +template +concept OpaqueHandle = std::equality_comparable && requires(H handle) { + { handle.index } -> std::convertible_to; + { handle.generation } -> std::convertible_to; +}; + +template struct GenericHandle final { + static constexpr uint32_t invalid_index = + std::numeric_limits::max(); + + uint32_t index = invalid_index; + uint32_t generation = 0; + + [[nodiscard]] constexpr bool valid() const noexcept { + return index != invalid_index; + } + + [[nodiscard]] friend constexpr bool + operator==(GenericHandle, GenericHandle) noexcept = default; +}; + +static_assert(OpaqueHandle>); + +} // namespace util \ No newline at end of file diff --git a/include/quark/vk/device/details/device_handle.hpp b/include/quark/vk/device/details/device_handle.hpp index dedf8f9..b95c697 100644 --- a/include/quark/vk/device/details/device_handle.hpp +++ b/include/quark/vk/device/details/device_handle.hpp @@ -1,21 +1,11 @@ #pragma once -#include +#include namespace quark::vk { -struct DeviceHandle { - uint32_t index = 0xFFFF'FFFFU; - uint32_t generation = 0; +struct DeviceHandleTag; - [[nodiscard]] constexpr bool valid() const noexcept { - return index != 0xFFFF'FFFFU; - } - - [[nodiscard]] friend constexpr bool operator==(DeviceHandle a, - DeviceHandle b) noexcept { - return a.index == b.index && a.generation == b.generation; - } -}; +using DeviceHandle = util::GenericHandle; } // namespace quark::vk diff --git a/include/quark/vk/device/details/device_registry.hpp b/include/quark/vk/device/details/device_registry.hpp index 227c0fd..80ace88 100644 --- a/include/quark/vk/device/details/device_registry.hpp +++ b/include/quark/vk/device/details/device_registry.hpp @@ -3,42 +3,10 @@ #include "device.hpp" #include "device_handle.hpp" -#include -#include -#include -#include +#include namespace quark::vk { -class DeviceRegistry final { -public: - DeviceRegistry() = default; - ~DeviceRegistry() = default; - - QUARK_MOVE_ONLY(DeviceRegistry); - - void clear() noexcept; - - [[nodiscard]] util::Result create(const Device::CreateInfo &ci); - void destroy(DeviceHandle handle) noexcept; - - [[nodiscard]] bool alive(DeviceHandle handle) const noexcept; - - Device *get(DeviceHandle handle) noexcept; - [[nodiscard]] const Device *get(DeviceHandle handle) const noexcept; - -private: - struct Slot { - Device device; - uint32_t generation = 1; - bool live = false; - }; - - [[nodiscard]] static bool matches_(DeviceHandle handle, - const Slot &s) noexcept; - - std::vector slots_; - std::vector free_; -}; +using DeviceRegistry = util::GenerationalRegistry; } // namespace quark::vk diff --git a/include/quark/vk/device/device_bundle.hpp b/include/quark/vk/device/device_bundle.hpp index 2a6908b..f444dc4 100644 --- a/include/quark/vk/device/device_bundle.hpp +++ b/include/quark/vk/device/device_bundle.hpp @@ -1,6 +1,8 @@ #pragma once #include "quark/utils/result.hpp" +#include +#include #include #include #include @@ -11,7 +13,10 @@ namespace quark::vk { class DeviceBundle final { public: DeviceBundle() = default; + explicit DeviceBundle(std::pmr::memory_resource *memory_resource); explicit DeviceBundle(const Device::CreateInfo &ci); + DeviceBundle(const Device::CreateInfo &ci, + std::pmr::memory_resource *memory_resource); ~DeviceBundle() { destroy(); } QUARK_MOVE_ONLY(DeviceBundle); diff --git a/include/quark/vk/instance/details/debug_messenger.hpp b/include/quark/vk/instance/details/debug_messenger.hpp index b18196a..081c7e2 100644 --- a/include/quark/vk/instance/details/debug_messenger.hpp +++ b/include/quark/vk/instance/details/debug_messenger.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include diff --git a/include/quark/vk/instance/details/instance_handle.hpp b/include/quark/vk/instance/details/instance_handle.hpp index 0b7b930..a8a6083 100644 --- a/include/quark/vk/instance/details/instance_handle.hpp +++ b/include/quark/vk/instance/details/instance_handle.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace quark::vk { @@ -9,18 +9,8 @@ namespace quark::vk { * * Generation prevents use-after-free when slots are reused. */ -struct InstanceHandle { - uint32_t index = 0xFFFF'FFFFU; - uint32_t generation = 0; +struct InstanceHandleTag; - [[nodiscard]] constexpr bool valid() const noexcept { - return index != 0xFFFF'FFFFU; - } - - [[nodiscard]] friend constexpr bool operator==(InstanceHandle a, - InstanceHandle b) noexcept { - return a.index == b.index && a.generation == b.generation; - } -}; +using InstanceHandle = util::GenericHandle; } // namespace quark::vk diff --git a/include/quark/vk/instance/details/instance_registry.hpp b/include/quark/vk/instance/details/instance_registry.hpp index c103724..2655148 100644 --- a/include/quark/vk/instance/details/instance_registry.hpp +++ b/include/quark/vk/instance/details/instance_registry.hpp @@ -1,45 +1,11 @@ #pragma once -#include -#include -#include +#include #include #include -#include -#include namespace quark::vk { -class InstanceRegistry final { -public: - InstanceRegistry() = default; - ~InstanceRegistry() = default; - - QUARK_MOVE_ONLY(InstanceRegistry); - - void clear() noexcept; - - [[nodiscard]] util::Result - create(const Instance::CreateInfo &ci); - void destroy(InstanceHandle handle) noexcept; - - [[nodiscard]] bool alive(InstanceHandle handle) const noexcept; - - Instance *get(InstanceHandle handle) noexcept; - [[nodiscard]] const Instance *get(InstanceHandle handle) const noexcept; - -private: - struct Slot { - Instance instance; - uint32_t generation = 1; - bool live = false; - }; - - [[nodiscard]] static bool matches_(InstanceHandle handle, - const Slot &s) noexcept; - - std::vector slots_; - std::vector free_; -}; +using InstanceRegistry = util::GenerationalRegistry; } // namespace quark::vk diff --git a/include/quark/vk/instance/instance_bundle.hpp b/include/quark/vk/instance/instance_bundle.hpp index bd63359..8907f26 100644 --- a/include/quark/vk/instance/instance_bundle.hpp +++ b/include/quark/vk/instance/instance_bundle.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -13,6 +15,7 @@ namespace quark::vk { class InstanceBundle final { public: InstanceBundle() = default; + explicit InstanceBundle(std::pmr::memory_resource *memory_resource); ~InstanceBundle() { destroy(); } // TODO: assert safe move semantics in instance and debug messenger diff --git a/src/backend/vulkan/device/device.cpp b/src/backend/vulkan/device/device.cpp index 28f63af..a331692 100644 --- a/src/backend/vulkan/device/device.cpp +++ b/src/backend/vulkan/device/device.cpp @@ -1,13 +1,19 @@ -#include +#include "quark/utils/diagnostic.hpp" +#include "quark/utils/error_types.hpp" +#include "quark/utils/result.hpp" +#include "quark/vk/vk_error.hpp" #include #include #include #include #include #include +#include #include -#include #include +#ifdef __APPLE__ +#include // for std::find, c++23 'std::ranges::contains' is not in apple clang, irritating! +#endif using std::vector; @@ -79,7 +85,7 @@ has_required_extensions(VkPhysicalDevice physical_device, vector props; QUARK_TRY_ASSIGN(props, enumerate_device_extensions(physical_device)); - for (const char *name : required_extensions) { + for (const char *const name : required_extensions) { QUARK_ENSURE(name != nullptr, QUARK_ERR(util::Errc::InvalidArg, "required extension name is null")); QUARK_ENSURE( @@ -240,11 +246,12 @@ build_device_extensions(VkPhysicalDevice physical_device, vector enabled = required; -#if defined(__APPLE__) +#ifdef __APPLE__ // MoltenVK usually needs portability subset; constexpr const char *kPortabilitySubset = "VK_KHR_portability_subset"; if (has_device_extension_props(props, kPortabilitySubset) && - !std::ranges::contains(enabled, kPortabilitySubset)) { + std::find(enabled.begin(), enabled.end(), kPortabilitySubset) == + enabled.end()) { enabled.push_back(kPortabilitySubset); QUARK_LOG_INFO("portability subset: enabled"); } diff --git a/src/backend/vulkan/device/device_bundle.cpp b/src/backend/vulkan/device/device_bundle.cpp index 2ccd278..0a8ac0c 100644 --- a/src/backend/vulkan/device/device_bundle.cpp +++ b/src/backend/vulkan/device/device_bundle.cpp @@ -1,12 +1,28 @@ +#include +#include +#include #include #include #include #include #include #include +#include namespace quark::vk { +DeviceBundle::DeviceBundle(std::pmr::memory_resource *memory_resource) + : registry_(memory_resource) {} + +DeviceBundle::DeviceBundle(const Device::CreateInfo &ci) + : DeviceBundle(ci, util::default_memory_resource()) {} + +DeviceBundle::DeviceBundle(const Device::CreateInfo &ci, + std::pmr::memory_resource *memory_resource) + : registry_(memory_resource) { + (void)create(ci); +} + util::Status DeviceBundle::create(const Device::CreateInfo &ci) { destroy(); QUARK_TRY_ASSIGN(handle_, registry_.create(ci)); @@ -19,32 +35,32 @@ void DeviceBundle::destroy() noexcept { } VkPhysicalDevice DeviceBundle::vk_physical_device() const noexcept { - const Device *device = registry_.get(handle_); + const Device *const device = registry_.get(handle_); return (device != nullptr) ? device->vk_physical_device() : VK_NULL_HANDLE; } VkDevice DeviceBundle::vk_device() const noexcept { - const Device *device = registry_.get(handle_); + const Device *const device = registry_.get(handle_); return (device != nullptr) ? device->vk_device() : VK_NULL_HANDLE; } VkQueue DeviceBundle::graphics_queue() const noexcept { - const Device *device = registry_.get(handle_); + const Device *const device = registry_.get(handle_); return (device != nullptr) ? device->graphics_queue() : VK_NULL_HANDLE; } VkQueue DeviceBundle::present_queue() const noexcept { - const Device *device = registry_.get(handle_); + const Device *const device = registry_.get(handle_); return (device != nullptr) ? device->present_queue() : VK_NULL_HANDLE; } uint32_t DeviceBundle::graphics_queue_family_index() const noexcept { - const Device *device = registry_.get(handle_); + const Device *const device = registry_.get(handle_); return (device != nullptr) ? device->graphics_queue_family_index() : 0; } uint32_t DeviceBundle::present_queue_family_index() const noexcept { - const Device *device = registry_.get(handle_); + const Device *const device = registry_.get(handle_); return (device != nullptr) ? device->present_queue_family_index() : 0; } diff --git a/src/backend/vulkan/device/device_registry.cpp b/src/backend/vulkan/device/device_registry.cpp index f1cc7b2..93e8219 100644 --- a/src/backend/vulkan/device/device_registry.cpp +++ b/src/backend/vulkan/device/device_registry.cpp @@ -1,97 +1 @@ -#include -#include -#include -#include -#include #include - -namespace quark::vk { - -bool DeviceRegistry::matches_(DeviceHandle handle, const Slot &s) noexcept { - return handle.valid() && s.live && s.generation == handle.generation; -} - -void DeviceRegistry::clear() noexcept { - for (auto &s : slots_) { - if (s.live) { - s.device.destroy(); - s.live = false; - ++s.generation; - } - } - - free_.clear(); - free_.reserve(slots_.size()); - for (uint32_t i = 0; i < slots_.size(); ++i) { - free_.push_back(i); - } -} - -util::Result -DeviceRegistry::create(const Device::CreateInfo &ci) { - uint32_t idx = 0; - - if (!free_.empty()) { - idx = free_.back(); - free_.pop_back(); - } else { - idx = static_cast(slots_.size()); - slots_.push_back(Slot{}); - } - - Slot &s = slots_[idx]; - - if (s.live) { - s.device.destroy(); - s.live = false; - ++s.generation; - } - - QUARK_TRY_STATUS(s.device.create(ci)); - s.live = true; - - return DeviceHandle{.index = idx, .generation = s.generation}; -} - -void DeviceRegistry::destroy(DeviceHandle handle) noexcept { - if (!handle.valid() || handle.index >= slots_.size()) { - return; - } - - Slot &s = slots_[handle.index]; - if (!matches_(handle, s)) { - return; - } - - s.device.destroy(); - s.live = false; - ++s.generation; - - free_.push_back(handle.index); -} - -bool DeviceRegistry::alive(DeviceHandle handle) const noexcept { - if (!handle.valid() || handle.index >= slots_.size()) { - return false; - } - - return matches_(handle, slots_[handle.index]); -} - -Device *DeviceRegistry::get(DeviceHandle handle) noexcept { - if (!alive(handle)) { - return nullptr; - } - - return &slots_[handle.index].device; -} - -const Device *DeviceRegistry::get(DeviceHandle handle) const noexcept { - if (!alive(handle)) { - return nullptr; - } - - return &slots_[handle.index].device; -} - -} // namespace quark::vk diff --git a/src/backend/vulkan/instance/debug_messenger.cpp b/src/backend/vulkan/instance/debug_messenger.cpp index 23744c9..d22d2cb 100644 --- a/src/backend/vulkan/instance/debug_messenger.cpp +++ b/src/backend/vulkan/instance/debug_messenger.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/backend/vulkan/instance/instance.cpp b/src/backend/vulkan/instance/instance.cpp index 1b6a45b..c19f66b 100644 --- a/src/backend/vulkan/instance/instance.cpp +++ b/src/backend/vulkan/instance/instance.cpp @@ -1,3 +1,6 @@ + +#include "quark/vk/instance/details/debug_messenger.hpp" +#include "quark/vk/vk_error.hpp" #include #include #include @@ -5,7 +8,8 @@ #include #include #include -#include +#include +#include namespace quark::vk { @@ -122,7 +126,7 @@ util::Status Instance::build_instance_extensions_( out_enable_debug_utils = false; bool enable_portability = false; -#if defined(__APPLE__) +#ifdef __APPLE__ constexpr bool portability_required = true; #else constexpr bool portability_required = false; @@ -166,7 +170,7 @@ util::Status Instance::create(const CreateInfo &ci) { VK_VERSION_PATCH(ci.api_version)); uint32_t loader_ver = VK_API_VERSION_1_0; -#if defined(VK_VERSION_1_1) +#ifdef VK_VERSION_1_1 vkEnumerateInstanceVersion(&loader_ver); #endif QUARK_LOG_INFO("loader api version: {}.{}.{}", VK_VERSION_MAJOR(loader_ver), diff --git a/src/backend/vulkan/instance/instance_bundle.cpp b/src/backend/vulkan/instance/instance_bundle.cpp index e627826..79dcff8 100644 --- a/src/backend/vulkan/instance/instance_bundle.cpp +++ b/src/backend/vulkan/instance/instance_bundle.cpp @@ -1,4 +1,5 @@ -#include + +#include #include #include #include @@ -8,6 +9,9 @@ namespace quark::vk { +InstanceBundle::InstanceBundle(std::pmr::memory_resource *memory_resource) + : registry_(memory_resource) {} + util::Status InstanceBundle::create(const Instance::CreateInfo &ci) { destroy(); @@ -21,12 +25,12 @@ void InstanceBundle::destroy() noexcept { } VkInstance InstanceBundle::vk_instance() const noexcept { - const Instance *instance = registry_.get(handle_); + const Instance *const instance = registry_.get(handle_); return (instance != nullptr) ? instance->handle() : VK_NULL_HANDLE; } const DebugMessenger *InstanceBundle::debug_messenger() const noexcept { - const Instance *instance = registry_.get(handle_); + const Instance *const instance = registry_.get(handle_); if (instance == nullptr) { return nullptr; } diff --git a/src/backend/vulkan/instance/instance_registry.cpp b/src/backend/vulkan/instance/instance_registry.cpp index 337bf1c..c66196d 100644 --- a/src/backend/vulkan/instance/instance_registry.cpp +++ b/src/backend/vulkan/instance/instance_registry.cpp @@ -1,98 +1 @@ -#include -#include -#include -#include -#include #include -#include - -namespace quark::vk { - -bool InstanceRegistry::matches_(InstanceHandle handle, const Slot &s) noexcept { - return handle.valid() && s.live && s.generation == handle.generation; -} - -void InstanceRegistry::clear() noexcept { - for (auto &s : slots_) { - if (s.live) { - s.instance.destroy(); - s.live = false; - ++s.generation; - } - } - - free_.clear(); - free_.reserve(slots_.size()); - for (uint32_t i = 0; i < slots_.size(); ++i) { - free_.push_back(i); - } -} - -util::Result -InstanceRegistry::create(const Instance::CreateInfo &ci) { - uint32_t idx = 0; - - if (!free_.empty()) { - idx = free_.back(); - free_.pop_back(); - } else { - idx = static_cast(slots_.size()); - slots_.push_back(Slot{}); - } - - Slot &s = slots_[idx]; - - if (s.live) { - s.instance.destroy(); - s.live = false; - ++s.generation; - } - - QUARK_TRY_STATUS(s.instance.create(ci)); - s.live = true; - - return InstanceHandle{.index = idx, .generation = s.generation}; -} - -void InstanceRegistry::destroy(InstanceHandle handle) noexcept { - if (!handle.valid() || handle.index >= slots_.size()) { - return; - } - - Slot &s = slots_[handle.index]; - if (!matches_(handle, s)) { - return; - } - - s.instance.destroy(); - s.live = false; - ++s.generation; - - free_.push_back(handle.index); -} - -bool InstanceRegistry::alive(InstanceHandle handle) const noexcept { - if (!handle.valid() || handle.index >= slots_.size()) { - return false; - } - - return matches_(handle, slots_[handle.index]); -} - -Instance *InstanceRegistry::get(InstanceHandle handle) noexcept { - if (!alive(handle)) { - return nullptr; - } - - return &slots_[handle.index].instance; -} - -const Instance *InstanceRegistry::get(InstanceHandle handle) const noexcept { - if (!alive(handle)) { - return nullptr; - } - - return &slots_[handle.index].instance; -} - -} // namespace quark::vk diff --git a/src/backend/vulkan/presentation/swapchain.cpp b/src/backend/vulkan/presentation/swapchain.cpp index d3cab85..e27ee05 100644 --- a/src/backend/vulkan/presentation/swapchain.cpp +++ b/src/backend/vulkan/presentation/swapchain.cpp @@ -1,10 +1,12 @@ #include +#include +#include +#include #include #include #include #include #include - namespace quark::vk { namespace { @@ -93,12 +95,6 @@ VkExtent2D choose_extent(const platform::IWindow &window, return actual_extent; } -void throw_if_vk(VkResult result, const char *what) { - if (result != VK_SUCCESS) { - throw std::runtime_error(what); - } -} - } // namespace void Swapchain::create(const CreateInfo &ci) { diff --git a/src/backend/vulkan/vulkan_context.cpp b/src/backend/vulkan/vulkan_context.cpp index 98e6777..e26fb2f 100644 --- a/src/backend/vulkan/vulkan_context.cpp +++ b/src/backend/vulkan/vulkan_context.cpp @@ -1,5 +1,10 @@ #include "vulkan_context.hpp" - +#include "quark/platform/window/IWindow.hpp" +#include "quark/utils/diagnostic.hpp" +#include "quark/utils/error_types.hpp" +#include "quark/utils/result.hpp" +#include "quark/vk/device/details/device.hpp" +#include "quark/vk/instance/details/instance.hpp" #include #include #include @@ -9,13 +14,12 @@ #include #include #include -#include #include #include #include #include #include -#include +#include using std::array; using std::vector; @@ -237,7 +241,7 @@ util::Status VulkanContext::run() { } void VulkanContext::create_window() { - auto window = std::make_unique(); + window_ = std::make_unique(); platform::IWindow::CreateInfo ci{}; ci.width = kWindowWidth; @@ -245,8 +249,7 @@ void VulkanContext::create_window() { ci.title = kWindowTitle.data(); ci.resizable = true; - window->create(ci); - window_ = std::move(window); + window_->create(ci); } util::Status VulkanContext::create_instance() { diff --git a/src/backend/vulkan/vulkan_context.hpp b/src/backend/vulkan/vulkan_context.hpp index a7352e5..be875e4 100644 --- a/src/backend/vulkan/vulkan_context.hpp +++ b/src/backend/vulkan/vulkan_context.hpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include using std::array; using std::vector; diff --git a/src/main.cpp b/src/main.cpp index dd3ff55..90299b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include "backend/vulkan/vulkan_context.hpp" +#include "quark/utils/error_types.hpp" #include #include diff --git a/src/platform/window/glfw_window.cpp b/src/platform/window/glfw_window.cpp index b06c3c1..f1237a0 100644 --- a/src/platform/window/glfw_window.cpp +++ b/src/platform/window/glfw_window.cpp @@ -1,6 +1,6 @@ +#include #define GLFW_INCLUDE_VULKAN #include -#include #include #include #include @@ -22,7 +22,7 @@ class GlfwVulkanSurfaceSource final : public vk::IVulkanSurfaceSource { [[nodiscard]] std::vector required_instance_extensions() const override { uint32_t count = 0; - const char **exts = glfwGetRequiredInstanceExtensions(&count); + const char **const exts = glfwGetRequiredInstanceExtensions(&count); if (exts == nullptr || count == 0) { throw std::runtime_error("glfwGetRequiredInstanceExtensions failed"); @@ -127,7 +127,7 @@ bool GlfwWindow::was_resized() const noexcept { return resized_; } void GlfwWindow::clear_resized() noexcept { resized_ = false; } void *GlfwWindow::query_interface(InterfaceId id) noexcept { - if (void *cached = iface_.find(id)) { + if (void *const cached = iface_.find(id)) { return cached; } @@ -152,7 +152,7 @@ void *GlfwWindow::query_interface(InterfaceId id) noexcept { } const void *GlfwWindow::query_interface(InterfaceId id) const noexcept { - if (const void *cached = iface_.find(id)) { + if (const void *const cached = iface_.find(id)) { return cached; } diff --git a/src/utils/diagnostics/diagnostic.cpp b/src/utils/diagnostics/diagnostic.cpp index 8aa38ce..ba71aaf 100644 --- a/src/utils/diagnostics/diagnostic.cpp +++ b/src/utils/diagnostics/diagnostic.cpp @@ -1,9 +1,10 @@ +#include "quark/utils/error_types.hpp" #include #include -#include #include #include #include +#include #include namespace util { @@ -13,18 +14,22 @@ using SinkList = std::vector; std::shared_ptr g_sinks; } // namespace +// NOLINTBEGIN void set_diagnostic_sinks(std::span sinks) noexcept { auto list = std::make_shared(sinks.begin(), sinks.end()); + std::atomic_store_explicit(&g_sinks, std::shared_ptr(std::move(list)), std::memory_order_release); } + std::shared_ptr diagnostic_sinks_snapshot() noexcept { - return std::atomic_load_explicit(std::addressof(g_sinks), + + return std::atomic_load_explicit(std::addressof(g_sinks), std::memory_order_acquire); } - +// NOLINTEND void report(const DiagnosticEvent &e) noexcept { auto list = diagnostic_sinks_snapshot(); if (list && !list->empty()) { diff --git a/src/utils/diagnostics/sinks/default_sink.cpp b/src/utils/diagnostics/sinks/default_sink.cpp index 55fd14a..a58c147 100644 --- a/src/utils/diagnostics/sinks/default_sink.cpp +++ b/src/utils/diagnostics/sinks/default_sink.cpp @@ -1,5 +1,6 @@ -#include -#include +#include "quark/utils/details/diagnostic_details.hpp" +#include "quark/utils/error_types.hpp" +#include #include namespace util { diff --git a/src/utils/diagnostics/sinks/spdlog_sink.cpp b/src/utils/diagnostics/sinks/spdlog_sink.cpp index e4e54ae..ac61db4 100644 --- a/src/utils/diagnostics/sinks/spdlog_sink.cpp +++ b/src/utils/diagnostics/sinks/spdlog_sink.cpp @@ -1,4 +1,5 @@ -#include +#include "quark/utils/error_types.hpp" +#include #include #include #include @@ -106,8 +107,8 @@ void spdlog_sink(void *ctx_ptr, const util::DiagnosticEvent &e) noexcept { } const auto &w = e.where; - spdlog::source_loc loc{w.file_name(), static_cast(w.line()), - w.function_name()}; + spdlog::source_loc const loc{w.file_name(), static_cast(w.line()), + w.function_name()}; if (!e.module.empty()) { ctx->file->log(loc, to_spd(e.severity), "[{}] {}", e.module, e.msg); diff --git a/vcpkg b/vcpkg index 8eafd12..e5a1490 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 8eafd12d19cbc42a4d5c34b5f2ce96d62e6ee9ff +Subproject commit e5a1490e1409d175932ef6014519e9ae149ddb7c