diff --git a/.clang-tidy b/.clang-tidy index 4bc6fa4..e50bece 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -50,8 +50,6 @@ 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 4d2b7e8..41153d3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ compile_commands.json .cache logs/ CMakeFiles/ -latex/ + # macOS .DS_Store @@ -13,5 +13,3 @@ 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 deleted file mode 100644 index b19dd54..0000000 --- a/docs/rfc/RFC-0001-opaque-generational-handles.md +++ /dev/null @@ -1,221 +0,0 @@ -# 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 deleted file mode 100644 index c8b80e2..0000000 --- a/include/quark/utils/allocator.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#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 d95db32..f09dae8 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 & /*e*/) noexcept; +void report(const DiagnosticEvent &) 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 deleted file mode 100644 index bcc6b1e..0000000 --- a/include/quark/utils/generational_registry.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#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 deleted file mode 100644 index 1b67e82..0000000 --- a/include/quark/utils/generic_handle.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#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 b95c697..dedf8f9 100644 --- a/include/quark/vk/device/details/device_handle.hpp +++ b/include/quark/vk/device/details/device_handle.hpp @@ -1,11 +1,21 @@ #pragma once -#include +#include namespace quark::vk { -struct DeviceHandleTag; +struct DeviceHandle { + uint32_t index = 0xFFFF'FFFFU; + uint32_t generation = 0; -using DeviceHandle = util::GenericHandle; + [[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; + } +}; } // namespace quark::vk diff --git a/include/quark/vk/device/details/device_registry.hpp b/include/quark/vk/device/details/device_registry.hpp index 80ace88..227c0fd 100644 --- a/include/quark/vk/device/details/device_registry.hpp +++ b/include/quark/vk/device/details/device_registry.hpp @@ -3,10 +3,42 @@ #include "device.hpp" #include "device_handle.hpp" -#include +#include +#include +#include +#include namespace quark::vk { -using DeviceRegistry = util::GenerationalRegistry; +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_; +}; } // namespace quark::vk diff --git a/include/quark/vk/device/device_bundle.hpp b/include/quark/vk/device/device_bundle.hpp index f444dc4..2a6908b 100644 --- a/include/quark/vk/device/device_bundle.hpp +++ b/include/quark/vk/device/device_bundle.hpp @@ -1,8 +1,6 @@ #pragma once #include "quark/utils/result.hpp" -#include -#include #include #include #include @@ -13,10 +11,7 @@ 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 081c7e2..b18196a 100644 --- a/include/quark/vk/instance/details/debug_messenger.hpp +++ b/include/quark/vk/instance/details/debug_messenger.hpp @@ -1,5 +1,6 @@ #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 a8a6083..0b7b930 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,8 +9,18 @@ namespace quark::vk { * * Generation prevents use-after-free when slots are reused. */ -struct InstanceHandleTag; +struct InstanceHandle { + uint32_t index = 0xFFFF'FFFFU; + uint32_t generation = 0; -using InstanceHandle = util::GenericHandle; + [[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; + } +}; } // namespace quark::vk diff --git a/include/quark/vk/instance/details/instance_registry.hpp b/include/quark/vk/instance/details/instance_registry.hpp index 2655148..c103724 100644 --- a/include/quark/vk/instance/details/instance_registry.hpp +++ b/include/quark/vk/instance/details/instance_registry.hpp @@ -1,11 +1,45 @@ #pragma once -#include +#include +#include +#include #include #include +#include +#include namespace quark::vk { -using InstanceRegistry = util::GenerationalRegistry; +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_; +}; } // namespace quark::vk diff --git a/include/quark/vk/instance/instance_bundle.hpp b/include/quark/vk/instance/instance_bundle.hpp index 8907f26..bd63359 100644 --- a/include/quark/vk/instance/instance_bundle.hpp +++ b/include/quark/vk/instance/instance_bundle.hpp @@ -1,7 +1,5 @@ #pragma once -#include -#include #include #include #include @@ -15,7 +13,6 @@ 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 a331692..28f63af 100644 --- a/src/backend/vulkan/device/device.cpp +++ b/src/backend/vulkan/device/device.cpp @@ -1,19 +1,13 @@ -#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 #include -#ifdef __APPLE__ -#include // for std::find, c++23 'std::ranges::contains' is not in apple clang, irritating! -#endif using std::vector; @@ -85,7 +79,7 @@ has_required_extensions(VkPhysicalDevice physical_device, vector props; QUARK_TRY_ASSIGN(props, enumerate_device_extensions(physical_device)); - for (const char *const name : required_extensions) { + for (const char *name : required_extensions) { QUARK_ENSURE(name != nullptr, QUARK_ERR(util::Errc::InvalidArg, "required extension name is null")); QUARK_ENSURE( @@ -246,12 +240,11 @@ build_device_extensions(VkPhysicalDevice physical_device, vector enabled = required; -#ifdef __APPLE__ +#if defined(__APPLE__) // MoltenVK usually needs portability subset; constexpr const char *kPortabilitySubset = "VK_KHR_portability_subset"; if (has_device_extension_props(props, kPortabilitySubset) && - std::find(enabled.begin(), enabled.end(), kPortabilitySubset) == - enabled.end()) { + !std::ranges::contains(enabled, kPortabilitySubset)) { 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 0a8ac0c..2ccd278 100644 --- a/src/backend/vulkan/device/device_bundle.cpp +++ b/src/backend/vulkan/device/device_bundle.cpp @@ -1,28 +1,12 @@ -#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)); @@ -35,32 +19,32 @@ void DeviceBundle::destroy() noexcept { } VkPhysicalDevice DeviceBundle::vk_physical_device() const noexcept { - const Device *const device = registry_.get(handle_); + const Device *device = registry_.get(handle_); return (device != nullptr) ? device->vk_physical_device() : VK_NULL_HANDLE; } VkDevice DeviceBundle::vk_device() const noexcept { - const Device *const device = registry_.get(handle_); + const Device *device = registry_.get(handle_); return (device != nullptr) ? device->vk_device() : VK_NULL_HANDLE; } VkQueue DeviceBundle::graphics_queue() const noexcept { - const Device *const device = registry_.get(handle_); + const Device *device = registry_.get(handle_); return (device != nullptr) ? device->graphics_queue() : VK_NULL_HANDLE; } VkQueue DeviceBundle::present_queue() const noexcept { - const Device *const device = registry_.get(handle_); + const Device *device = registry_.get(handle_); return (device != nullptr) ? device->present_queue() : VK_NULL_HANDLE; } uint32_t DeviceBundle::graphics_queue_family_index() const noexcept { - const Device *const device = registry_.get(handle_); + const Device *device = registry_.get(handle_); return (device != nullptr) ? device->graphics_queue_family_index() : 0; } uint32_t DeviceBundle::present_queue_family_index() const noexcept { - const Device *const device = registry_.get(handle_); + const Device *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 93e8219..f1cc7b2 100644 --- a/src/backend/vulkan/device/device_registry.cpp +++ b/src/backend/vulkan/device/device_registry.cpp @@ -1 +1,97 @@ +#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 d22d2cb..23744c9 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 c19f66b..1b6a45b 100644 --- a/src/backend/vulkan/instance/instance.cpp +++ b/src/backend/vulkan/instance/instance.cpp @@ -1,6 +1,3 @@ - -#include "quark/vk/instance/details/debug_messenger.hpp" -#include "quark/vk/vk_error.hpp" #include #include #include @@ -8,8 +5,7 @@ #include #include #include -#include -#include +#include namespace quark::vk { @@ -126,7 +122,7 @@ util::Status Instance::build_instance_extensions_( out_enable_debug_utils = false; bool enable_portability = false; -#ifdef __APPLE__ +#if defined(__APPLE__) constexpr bool portability_required = true; #else constexpr bool portability_required = false; @@ -170,7 +166,7 @@ util::Status Instance::create(const CreateInfo &ci) { VK_VERSION_PATCH(ci.api_version)); uint32_t loader_ver = VK_API_VERSION_1_0; -#ifdef VK_VERSION_1_1 +#if defined(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 79dcff8..e627826 100644 --- a/src/backend/vulkan/instance/instance_bundle.cpp +++ b/src/backend/vulkan/instance/instance_bundle.cpp @@ -1,5 +1,4 @@ - -#include +#include #include #include #include @@ -9,9 +8,6 @@ namespace quark::vk { -InstanceBundle::InstanceBundle(std::pmr::memory_resource *memory_resource) - : registry_(memory_resource) {} - util::Status InstanceBundle::create(const Instance::CreateInfo &ci) { destroy(); @@ -25,12 +21,12 @@ void InstanceBundle::destroy() noexcept { } VkInstance InstanceBundle::vk_instance() const noexcept { - const Instance *const instance = registry_.get(handle_); + const Instance *instance = registry_.get(handle_); return (instance != nullptr) ? instance->handle() : VK_NULL_HANDLE; } const DebugMessenger *InstanceBundle::debug_messenger() const noexcept { - const Instance *const instance = registry_.get(handle_); + const Instance *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 c66196d..337bf1c 100644 --- a/src/backend/vulkan/instance/instance_registry.cpp +++ b/src/backend/vulkan/instance/instance_registry.cpp @@ -1 +1,98 @@ +#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 e27ee05..d3cab85 100644 --- a/src/backend/vulkan/presentation/swapchain.cpp +++ b/src/backend/vulkan/presentation/swapchain.cpp @@ -1,12 +1,10 @@ #include -#include -#include -#include #include #include #include #include #include + namespace quark::vk { namespace { @@ -95,6 +93,12 @@ 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 e26fb2f..98e6777 100644 --- a/src/backend/vulkan/vulkan_context.cpp +++ b/src/backend/vulkan/vulkan_context.cpp @@ -1,10 +1,5 @@ #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 @@ -14,12 +9,13 @@ #include #include #include +#include #include #include #include #include #include -#include +#include using std::array; using std::vector; @@ -241,7 +237,7 @@ util::Status VulkanContext::run() { } void VulkanContext::create_window() { - window_ = std::make_unique(); + auto window = std::make_unique(); platform::IWindow::CreateInfo ci{}; ci.width = kWindowWidth; @@ -249,7 +245,8 @@ void VulkanContext::create_window() { ci.title = kWindowTitle.data(); ci.resizable = true; - window_->create(ci); + window->create(ci); + window_ = std::move(window); } util::Status VulkanContext::create_instance() { diff --git a/src/backend/vulkan/vulkan_context.hpp b/src/backend/vulkan/vulkan_context.hpp index be875e4..a7352e5 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 90299b9..dd3ff55 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #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 f1237a0..b06c3c1 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 **const exts = glfwGetRequiredInstanceExtensions(&count); + const char **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 *const cached = iface_.find(id)) { + if (void *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 *const cached = iface_.find(id)) { + if (const void *cached = iface_.find(id)) { return cached; } diff --git a/src/utils/diagnostics/diagnostic.cpp b/src/utils/diagnostics/diagnostic.cpp index ba71aaf..8aa38ce 100644 --- a/src/utils/diagnostics/diagnostic.cpp +++ b/src/utils/diagnostics/diagnostic.cpp @@ -1,10 +1,9 @@ -#include "quark/utils/error_types.hpp" #include #include +#include #include #include #include -#include #include namespace util { @@ -14,22 +13,18 @@ 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 a58c147..55fd14a 100644 --- a/src/utils/diagnostics/sinks/default_sink.cpp +++ b/src/utils/diagnostics/sinks/default_sink.cpp @@ -1,6 +1,5 @@ -#include "quark/utils/details/diagnostic_details.hpp" -#include "quark/utils/error_types.hpp" -#include +#include +#include #include namespace util { diff --git a/src/utils/diagnostics/sinks/spdlog_sink.cpp b/src/utils/diagnostics/sinks/spdlog_sink.cpp index ac61db4..e4e54ae 100644 --- a/src/utils/diagnostics/sinks/spdlog_sink.cpp +++ b/src/utils/diagnostics/sinks/spdlog_sink.cpp @@ -1,5 +1,4 @@ -#include "quark/utils/error_types.hpp" -#include +#include #include #include #include @@ -107,8 +106,8 @@ void spdlog_sink(void *ctx_ptr, const util::DiagnosticEvent &e) noexcept { } const auto &w = e.where; - spdlog::source_loc const loc{w.file_name(), static_cast(w.line()), - w.function_name()}; + spdlog::source_loc 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 e5a1490..8eafd12 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit e5a1490e1409d175932ef6014519e9ae149ddb7c +Subproject commit 8eafd12d19cbc42a4d5c34b5f2ce96d62e6ee9ff