diff --git a/include/avk/avk.hpp b/include/avk/avk.hpp index 72f3915..8cfa0f1 100644 --- a/include/avk/avk.hpp +++ b/include/avk/avk.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -975,6 +976,22 @@ namespace avk #pragma region semaphore static semaphore create_semaphore(vk::Device aDevice, const DISPATCH_LOADER_CORE_TYPE& aDispatchLoader, std::function aAlterConfigBeforeCreation = {}); semaphore create_semaphore(std::function aAlterConfigBeforeCreation = {}); + /** + * @brief Creates a timeline semaphore + * @param aPayload (optional) The initial value of the payload. Defaults to 0. + * @param aAlterConfigBeforeCreation (optional) Use it to alter the timeline semaphore configuration before it is actually being created. + * @return The created semaphore. + */ + semaphore create_timeline_semaphore(uint64_t aPayload = 0, std::function aAlterConfigBeforeCreation = {}); + + /** + * @brief Waits on host until the condition specified with the parameters is met. + * @param aSemaphoreValueInfos Span of semaphore_value_info structs, each containing a semaphore and a payload value to wait on. All semaphores are required to be owned by the same logical device. + * @param aWaitOnAll (optional) If true, waits until ALL semaphores have reached their target timestamps. If false, waits until ANY semaphore has reached its target timestamp. + * @param aTimeout (optional) Defines a timeout (in nanoseconds) after which the function returns regardless of the semaphore state. + * @return Value of type vk::Result containing information about whether the wait operation succeeded, or the timeout has been triggered. + */ + static vk::Result wait_until_signaled(std::span aSemaphoreValueInfos, bool aWaitOnAll = true, std::optional aTimeout = {}); #pragma endregion #pragma region shader diff --git a/include/avk/commands.hpp b/include/avk/commands.hpp index b5c723c..4c1fa69 100644 --- a/include/avk/commands.hpp +++ b/include/avk/commands.hpp @@ -1235,31 +1235,132 @@ namespace avk #endif } + /** Helper struct to specify a semaphore and a value for it. + * This is used when waiting for / signaling timeline semaphores. + * + * Can be created in the following ways: + * 1) sem = val + * 2) sem >= val + * + * Case 1) makes sense when signaling semaphores, while case 2) makes sense when waiting for semaphores. + */ + struct semaphore_value_info + { + avk::resource_argument mSemaphore; + uint64_t mValue; + }; + inline auto operator>=(avk::resource_argument aSemaphore, uint64_t aValue) -> semaphore_value_info { + return semaphore_value_info{ std::move(aSemaphore), aValue }; + } + /** + * @brief Info struct that defines blocking behavior on the gpu at specific pipeline stages for a specific semaphore + * + * Can be created in the following ways: + * 1) sem >> pipelineFlags + * 2) sem >= val >> pipelineFlags + * 3) (sem >= val) >> pipelineFlags + * + * sem... the semaphore to wait for + * val... which semaphore value to wait on (timeline semaphores only) + * pipelineFlags... defines which pipeline stages should wait for the semaphore + */ struct semaphore_wait_info { avk::resource_argument mWaitSemaphore; avk::stage::pipeline_stage_flags mDstStage; + uint64_t mValue; }; - inline semaphore_wait_info operator>> (avk::resource_argument a, avk::stage::pipeline_stage_flags b) + inline auto operator>> (avk::resource_argument aSemaphore, avk::stage::pipeline_stage_flags aStageFlags) -> semaphore_wait_info + { + return semaphore_wait_info{ std::move(aSemaphore), aStageFlags, 0 }; + } + + inline auto operator>> (semaphore_value_info aSemaphoreValueInfo, avk::stage::pipeline_stage_flags aStageFlags) -> semaphore_wait_info { - return semaphore_wait_info{ std::move(a), b }; + return semaphore_wait_info{ std::move(aSemaphoreValueInfo.mSemaphore), aStageFlags, aSemaphoreValueInfo.mValue }; + } + + /** + * Allows `waitSemaphore >= waitValue >> pipelineStageFlags` without parentheses around `>=`. + * Requires `operator>>(uint64_t, avk::stage::pipeline_stage_flags) -> semaphore_wait_info` to work + * + * Unwanted side effect: `waitValue >> pipelineStageFlags` compiles but produces an invalid semaphore_wait_info + */ + inline auto operator>=(avk::resource_argument aSemaphore, semaphore_wait_info aSemWaitInfo) -> semaphore_wait_info { + aSemWaitInfo.mWaitSemaphore = std::move(aSemaphore); + return aSemWaitInfo; } + /** + * Allows `waitSemaphore >= waitValue >> pipelineStageFlags` without parentheses around `>=` + * Requires `operator>>(uint64_t, avk::stage::pipeline_stage_flags) -> semaphore_wait_info` to work + * + * Unwanted side effect: `waitValue >> pipelineStageFlags` compiles but produces an invalid semaphore_wait_info + */ + inline auto operator>=(avk::owning_resource aSemaphore, semaphore_wait_info aSemWaitInfo)->semaphore_wait_info { + aSemWaitInfo.mWaitSemaphore = std::move(aSemaphore); + return aSemWaitInfo; + } + } // namespace avk + + /** + * Allows `waitSemaphore >= waitValue >> pipelineStageFlags` without parentheses around `>=` + * Requires `operator>=(avk::resource_argument, semaphore_wait_info) -> semaphore_wait_info` to work + * + * Unwanted side effect: `waitValue >> pipelineStageFlags` compiles but produces an invalid semaphore_wait_info + * @note This operator overload is defined in global scope because it weirdly wasn't found by auto_vk_toolkit applications otherwise. + */ + inline auto operator>>(uint64_t aValue, avk::stage::pipeline_stage_flags aStageFlags) -> avk::semaphore_wait_info { + return avk::semaphore_wait_info{ avk::owning_resource(), aStageFlags, aValue}; + } + namespace avk { + + /** + * @brief Info struct that defines semaphore signaling bahavior after specific pipeline stages have concluded + * + * Can be created in the following ways: + * 1) pipelineFlags >> sem + * 2) pipelineFlags >> sem = val + * 3) pipelineFlags >> (sem = val) + * + * pipelineFlags... defines which pipeline stages must be cleared before the semaphore may be signaled + * sem... the semaphore to signal + * val... the value to signal the semaphore to (timeline semaphores only) + */ struct semaphore_signal_info { avk::stage::pipeline_stage_flags mSrcStage; avk::resource_argument mSignalSemaphore; + uint64_t mValue; + + /** + * @brief Allows `pipelineFlags >> sem = val` + * + * Due to right associativity of operator= shouldn't cause unwanted side-effects. + * + * @note This does not cover the case `pipelineFlags >> (sem = val)`. + * To allow this ^, explicit template specializations of owning_resource and resource_argument for semaphore_t are defined in semaphore.hpp + * + */ + auto operator=(uint64_t aValue) -> semaphore_signal_info& { + mValue = aValue; + return *this; + } }; - inline semaphore_signal_info operator>> (avk::stage::pipeline_stage_flags a, avk::resource_argument b) + inline auto operator>> (avk::stage::pipeline_stage_flags aStageFlags, avk::resource_argument aSemaphore) -> semaphore_signal_info { - return semaphore_signal_info{ a, std::move(b) }; + return semaphore_signal_info{ aStageFlags, std::move(aSemaphore), 0 }; } + inline auto operator>> (avk::stage::pipeline_stage_flags aStageFlags, semaphore_value_info aSemaphoreValueInfo) -> semaphore_signal_info + { + return semaphore_signal_info{ aStageFlags, std::move(aSemaphoreValueInfo.mSemaphore), aSemaphoreValueInfo.mValue }; + } class recorded_command_buffer; diff --git a/include/avk/cpp_utils.hpp b/include/avk/cpp_utils.hpp index f253c57..f481118 100644 --- a/include/avk/cpp_utils.hpp +++ b/include/avk/cpp_utils.hpp @@ -340,6 +340,12 @@ namespace avk std::end(x); }; + class semaphore_t; // defined in semaphore.hpp + template requires non_const + class owning_resource; + template<> + class owning_resource; // defined in semaphore.hpp + // This class represents a/the owner of a specific resource T. // // The resource is either held locally on the stack, or -- as an additional features -- moved onto @@ -579,6 +585,12 @@ namespace avk } }; + + template + class resource_argument; + template<> + class resource_argument; // defined in semaphore.hpp + // A type for passing resources as arguments. Can be used to express that // ownership shall be passed along with it, or only a reference to it. template diff --git a/include/avk/semaphore.hpp b/include/avk/semaphore.hpp index 1c614a0..d57f05e 100644 --- a/include/avk/semaphore.hpp +++ b/include/avk/semaphore.hpp @@ -6,6 +6,8 @@ namespace avk // Forward declaration: class queue; + struct semaphore_value_info; + /** A synchronization object which allows GPU->GPU synchronization */ class semaphore_t { @@ -41,13 +43,30 @@ namespace avk return *this; } - semaphore_t& handle_lifetime_of(any_owning_resource_t aResource); + semaphore_t& handle_lifetime_of(any_owning_resource_t aResource, uint64_t aDeleteResourceAtValue = std::numeric_limits::max()); const auto& create_info() const { return mCreateInfo; } auto& create_info() { return mCreateInfo; } const auto& handle() const { return mSemaphore.get(); } const auto* handle_addr() const { return &mSemaphore.get(); } + // timeline semaphore specific functions + + /** @brief Destroys outdated resources which are handled by this timeline semaphore. + */ + void cleanup_expired_resources(); + + /** @brief returns the current value of the timeline semaphore */ + const uint64_t query_current_value() const; + + /** @brief sets the timeline semaphore to the specified value */ + void signal(uint64_t aNewValue) const; + + /** @brief Waits on host until the timiline semaphore reaches the given value or the timeout(in nanoseconds) happens. + * @return Value of type vk::Result containing information about whether the wait operation succeeded, or the timeout has been triggered. + */ + void wait_until_signaled(uint64_t aSignalValue, std::optional aTimeout = {}) const; + private: // The semaphore config struct: vk::SemaphoreCreateInfo mCreateInfo; @@ -57,9 +76,380 @@ namespace avk /** A custom deleter function called upon destruction of this semaphore */ std::optional> mCustomDeleter; - std::vector mLifetimeHandledResources; + std::forward_list> mLifetimeHandledResources; + }; + + /** + * @brief This is an explicit template specialization of owning_resource as defined in cpp_utils.hpp + * It is a complete duplicate of the class that contains an additional overload for operator= (defined at the bottom). + * + * The definition of an explicit template specialization is required since operator= overloads cannot be defined as non-member functions. + * (see https://en.cppreference.com/w/cpp/language/operators) + * As far as I can tell, defining an explicit template specialization for a class requires duplicating the entire class even if only minor changes are made. + * + * + * The overload allows instantiating semaphore_value_info structs with the expression `sem = val`. + * Where the type of sem is `owning_resource` and the type of val is `uint64_t`. + */ + template<> + class owning_resource : public std::variant> + { + private: + using T = semaphore_t; + public: + // The type of the resource being owned and handled + using value_type = T; + + // Cast the this-pointer to what it is: a std::variant-pointer (and to const) + const std::variant>* this_as_variant() const noexcept + { + return static_cast>*>(this); + } + + // Cast the this-pointer to what it is: a std::variant-pointer + std::variant>* this_as_variant() noexcept + { + return static_cast>*>(this); + } + + // Construct an owning_resource instance and initialize it to std::monostate. + owning_resource() + : std::variant>{} + {} + + // Construct an owning_resource by rvalue reference of a resource T. + // The original resource that is moved from might not be modified by the move. + // By default, an owning_resource is created with shared ownership enabled. + owning_resource(T&& aResource, bool aCreateWithSharedOwnershipEnabled = true) noexcept + { + if (aCreateWithSharedOwnershipEnabled) { + *this_as_variant() = std::make_shared(std::move(aResource)); + } + else { + *this_as_variant() = std::move(aResource); + } + } + + owning_resource& operator=(T&& aResource) = delete; + //// Move-assign an owning_resource from an rvalue reference of a resource T. + //// The original resource that is moved from might not be modified by the move. + //// By default, an owning_resource is created with shared ownership enabled. + //owning_resource& operator=(T&& aResource) noexcept + //{ + // if (aCreateWithSharedOwnershipEnabled) { + // *this_as_variant() = std::make_shared(std::move(aResource)); + // } + // else { + // *this_as_variant() = std::move(aResource); + // } + // return *this; + //} + + // Move-construct an owning_resource from another owning_resource. + // The other owning_reference is reset to std::monostate. + owning_resource(owning_resource&& aOther) noexcept + : std::variant>{ std::move(aOther) } + { + if (aOther.has_value()) { + aOther = owning_resource{}; + } + } + + // Move-assign an owning_resource from another owning_resource. + // The other owning_reference is reset to std::monostate. + owning_resource& operator=(owning_resource&& aOther) noexcept + { + *this_as_variant() = std::move(aOther); + if (aOther.has_value()) { + aOther = owning_resource{}; + } + return *this; + } + + // Resource types are expected to be move-only types. Therefore, an owning_resource + // can not be created by copying a resource. + owning_resource(const T&) = delete; + + // Resource types are expected to be move-only types. Therefore, an owning_resource + // can not be assigned a copy of a resource. + owning_resource& operator=(const T&) = delete; + + // owning_resources not always can be copy-constructed. But when they can, they + // can only due to a preceeding call to enable_shared_ownership, which moves the + // resource onto the heap. + owning_resource(const owning_resource& aOther) + { + if (aOther.has_value()) { + if (!aOther.is_shared_ownership_enabled()) { + // + // Why are you getting this exception? + // The most obvious reason would be that you have intentionally + // tried to copy a resource which might not be allowed unless + // "shared ownership" has been enabled. + // If you intended to enable shared ownership, call: `enable_shared_ownership()` + // which will move the resource internally into a shared pointer. + // + // Attention: + // A common source of unintentionally causing this exception is + // the usage of a resource in an `std::initializer_list`. The problem + // with that is that it looks like it does not support move-only types. + // An alternative to initializer lists would be to use `ak::make_vector`. + // + throw avk::logic_error("You are trying to copy-construct a resource of type '" + std::string(typeid(T).name()) + "' which does not have shared ownership enabled. This call will fail now. You can try to use owning_resource::enable_shared_ownership()."); + } + *this_as_variant() = std::get>(aOther); + } + else { + assert(!has_value()); + } + } + + // owning_resources not always can be copy-assigned. But when they can, they + // can only due to a preceeding call to enable_shared_ownership, which moves the + // resource onto the heap. + owning_resource& operator=(const owning_resource& aOther) + { + if (aOther.has_value()) { + if (!aOther.is_shared_ownership_enabled()) { + // + // Why are you getting this exception? + // The most obvious reason would be that you have intentionally + // tried to copy a resource which might not be allowed unless + // "shared ownership" has been enabled. + // If you intended to enable shared ownership, call: `enable_shared_ownership()` + // which will move the resource internally into a shared pointer. + // + // Attention: + // A common source of unintentionally causing this exception is + // the usage of a resource in an `std::initializer_list`. The problem + // with that is that it looks like it does not support move-only types. + // An alternative to initializer lists would be to use `ak::make_vector`. + // + throw avk::logic_error("Can only copy assign owning_resources which have shared ownership enabled."); + } + *this_as_variant() = std::get>(aOther); + } + else { + assert(!has_value()); + } + return *this; + } + + // Nothing wrong with default destruction + ~owning_resource() = default; + + // Enable shared ownership of this resource. That means: Transfer the resource + // from the stack to the heap and manage its lifetime through a shared pointer. + // (Unless it already has been.) + void enable_shared_ownership() + { + if (is_shared_ownership_enabled()) { + return; // Already established + } + if (std::holds_alternative(*this)) { + throw avk::logic_error("This owning_resource is uninitialized, i.e. std::monostate."); + } + *this_as_variant() = std::make_shared(std::move(std::get(*this))); + } + + // Has this owning_resource instance shared ownership enabled. + // Or put differently: Is the resource T living on the heap and + // and referenced via a shared pointer? + bool is_shared_ownership_enabled() const noexcept + { + return std::holds_alternative>(*this); + } + + // Does this owning_resource store the resource on the stack? + bool holds_item_directly() const noexcept + { + return std::holds_alternative(*this); + } + + // Does this owning_resource actually store a resource? + // (Or does it refer to std::monostate?) + bool has_value() const noexcept + { + return !std::holds_alternative(*this); + } + + // Get a const reference to the owned resource T + const T& get() const + { + if (holds_item_directly()) { return std::get(*this_as_variant()); } + if (is_shared_ownership_enabled()) { return *std::get>(*this_as_variant()); } + throw avk::logic_error("This owning_resource is uninitialized, i.e. std::monostate."); + } + + // Get a reference to the owned resource T + T& get() + { + if (is_shared_ownership_enabled()) { return *std::get>(*this_as_variant()); } + if (holds_item_directly()) { return std::get(*this_as_variant()); } + throw avk::logic_error("This owning_resource is uninitialized, i.e. std::monostate."); + } + + [[nodiscard]] const T& as_reference() const + { + return get(); + } + + [[nodiscard]] T& as_reference() + { + return get(); + } + + // Access the resource by returning a reference to it + const T& operator*() const + { + return get(); + } + + // Access the resource by returning a reference to it + T& operator*() + { + return get(); + } + + // Access the resource by returning its address + const T* operator->() const + { + return &get(); + } + + // Access the resource by returning its address + T* operator->() + { + return &get(); + } + + semaphore_value_info operator=(uint64_t aValue); }; + // Typedef for a variable representing an owner of a semaphore using semaphore = avk::owning_resource; + + /** + * @brief This is an explicit template specialization of resource_argument as defined in cpp_utils.hpp + * It is a complete duplicate of the class that contains an additional overload for operator= (defined at the bottom). + * + * The definition of an explicit template specialization is required since operator= overloads cannot be defined as non-member functions. + * (see https://en.cppreference.com/w/cpp/language/operators) + * As far as I can tell, defining an explicit template specialization for a class requires duplicating the entire class even if only minor changes are made. + * + * The overload allows instantiating semaphore_value_info structs with the expression `sem = val`. + * Where the type of sem is `resource_argument` and the type of val is `uint64_t`. + */ + template<> + class resource_argument : public std::variant, std::reference_wrapper, owning_resource> + { + private: + using T = semaphore_t; + public: + // The type of the resource: + using value_type = T; + + // Cast the this-pointer to what it is: a std::variant-pointer (and to const) + const std::variant, std::reference_wrapper, owning_resource>* this_as_variant() const noexcept + { + return static_cast, std::reference_wrapper, owning_resource>*>(this); + } + + // Cast the this-pointer to what it is: a std::variant-pointer + std::variant, std::reference_wrapper, owning_resource>* this_as_variant() noexcept + { + return static_cast, std::reference_wrapper, owning_resource>*>(this); + } + + resource_argument(const T& aResource) noexcept + : std::variant, std::reference_wrapper, owning_resource>{ std::cref(aResource) } + { } + + resource_argument(T& aResource) noexcept + : std::variant, std::reference_wrapper, owning_resource>{ std::ref(aResource) } + { } + + resource_argument(owning_resource aResource) noexcept + : std::variant, std::reference_wrapper, owning_resource>{ std::move(aResource) } + { } + + resource_argument(resource_argument&&) noexcept = default; + resource_argument(const resource_argument& aOther) = default; + resource_argument& operator=(resource_argument&&) noexcept = default; + resource_argument& operator=(const resource_argument&) = default; + ~resource_argument() = default; + + bool is_ownership() const { + return std::holds_alternative>(*this_as_variant()); + } + + bool is_reference() const { + return !is_ownership(); + } + + // Get a reference to the owned resource T + T& get() + { + if (is_ownership()) { + return std::get>(*this_as_variant()).get(); + } + if (std::holds_alternative>(*this_as_variant())) { + return std::get>(*this_as_variant()); + } + else { + throw avk::logic_error("The resource of type '" + std::string(typeid(T).name()) + "' is stored as const reference. Cannot return it as non-const reference. Use ::get_const_reference instead!"); + } + } + + // Get a reference to the owned resource T + const T& get_const_reference() const + { + if (is_ownership()) { + return std::get>(*this_as_variant()).get(); + } + if (std::holds_alternative>(*this_as_variant())) { + return std::get>(*this_as_variant()); + } + else { + assert(std::holds_alternative>(*this_as_variant())); + return std::get>(*this_as_variant()); + } + } + + // Access the resource by returning a const reference to it + const T& operator*() const + { + return get_const_reference(); + } + + // Access the resource by returning its address to const + const T* operator->() const + { + return &get_const_reference(); + } + + // Get the ownership, i.e. the owned resource: + owning_resource& get_ownership() + { + if (is_reference()) { + throw avk::logic_error("The resource of type '" + std::string(typeid(T).name()) + "' is stored as reference. Cannot get its parent (owning) resource."); + } + assert(is_ownership()); + return std::get>(*this_as_variant()); + } + + // Get the ownership or an empty ownership object. + // This method does not throw. + owning_resource move_ownership_or_get_empty() + { + if (is_reference()) { + return owning_resource{}; + } + assert(is_ownership()); + return std::move(std::get>(*this_as_variant())); + } + + semaphore_value_info operator=(uint64_t aValue); + }; } diff --git a/src/avk.cpp b/src/avk.cpp index d15e219..743b4b6 100644 --- a/src/avk.cpp +++ b/src/avk.cpp @@ -7127,11 +7127,81 @@ namespace avk return create_semaphore(device(), dispatch_loader_core(), std::move(aAlterConfigBeforeCreation)); } - semaphore_t& semaphore_t::handle_lifetime_of(any_owning_resource_t aResource) + semaphore root::create_timeline_semaphore(uint64_t aPayload, std::function aAlterConfigBeforeCreation) { - mLifetimeHandledResources.push_back(std::move(aResource)); + auto typeInfo = std::make_unique(); + typeInfo->semaphoreType = vk::SemaphoreType::eTimeline; + typeInfo->initialValue = aPayload; + + return create_semaphore(device(), dispatch_loader_core(), [otherAlterations = move(aAlterConfigBeforeCreation), &typeInfo](semaphore_t& aSem) { + aSem.create_info().pNext = typeInfo.get(); + + if (otherAlterations) { + otherAlterations(aSem); + } + }); + } + + vk::Result root::wait_until_signaled(std::span aSemaphoreValueInfos, bool aWaitOnAll, std::optional aTimeout) { + if (aSemaphoreValueInfos.empty()) { + return vk::Result::eSuccess; + } + + const vk::Device& device = aSemaphoreValueInfos.front().mSemaphore->mSemaphore.getOwner(); + std::vector semHandles; + std::vector timestampValues; + for (const auto& svi : aSemaphoreValueInfos) { + assert(device == svi.mSemaphore->mSemaphore.getOwner()); + semHandles.push_back(svi.mSemaphore->handle()); + timestampValues.push_back(svi.mValue); + } + + vk::SemaphoreWaitInfo info( + aWaitOnAll ? vk::SemaphoreWaitFlags{} : vk::SemaphoreWaitFlagBits::eAny, + static_cast(semHandles.size()), + semHandles.data(), + timestampValues.data() + ); + + return device.waitSemaphores(info, aTimeout.value_or(UINT64_MAX)); + } + + semaphore_t& semaphore_t::handle_lifetime_of(any_owning_resource_t aResource, uint64_t aDeleteResourceAtValue) + { + mLifetimeHandledResources.push_front(std::make_tuple(std::move(aResource), aDeleteResourceAtValue)); return *this; } + void semaphore_t::cleanup_expired_resources() { + uint64_t val = query_current_value(); + mLifetimeHandledResources.remove_if([val](auto& r) {return std::get<1>(r) <= val; }); + } + + const uint64_t semaphore_t::query_current_value() const { + uint64_t value; + auto result = mSemaphore.getOwner().getSemaphoreCounterValue(mSemaphore.get(), &value); + assert(static_cast(result) >= 0); + return value; + } + + void semaphore_t::signal(uint64_t aNewValue) const { + auto info = vk::SemaphoreSignalInfo{} + .setSemaphore(mSemaphore.get()) + .setValue(aNewValue); + mSemaphore.getOwner().signalSemaphore(info); + } + + void semaphore_t::wait_until_signaled(uint64_t aSignalValue, std::optional aTimeout) const { + vk::SemaphoreWaitInfo info{ + vk::SemaphoreWaitFlags{}, + 1u, + handle_addr(), + &aSignalValue + }; + mSemaphore.getOwner().waitSemaphores(info, aTimeout.value_or(UINT64_MAX)); + } + + semaphore_value_info owning_resource::operator=(uint64_t aValue) { return semaphore_value_info{get(), aValue}; } + semaphore_value_info resource_argument::operator=(uint64_t aValue) { return semaphore_value_info{get(), aValue}; } #pragma endregion #pragma region shader definitions @@ -8737,7 +8807,7 @@ namespace avk // Gather config for wait semaphores: std::vector waitSem; for (auto& semWait : mSemaphoreWaits) { - auto& subInfo = waitSem.emplace_back(semWait.mWaitSemaphore->handle()); // TODO: What about timeline semaphores? (see 'value' param!) + auto& subInfo = waitSem.emplace_back(semWait.mWaitSemaphore->handle(), semWait.mValue); std::visit(lambda_overload{ [&subInfo](const std::monostate&) { subInfo.setStageMask(vk::PipelineStageFlagBits2KHR::eNone); @@ -8770,7 +8840,7 @@ namespace avk // Gather config for signal semaphores: std::vector signalSem; for (auto& semSig : mSemaphoreSignals) { - auto& subInfo = signalSem.emplace_back(semSig.mSignalSemaphore->handle()); // TODO: What about timeline semaphores? (see 'value' param!) + auto& subInfo = signalSem.emplace_back(semSig.mSignalSemaphore->handle(), semSig.mValue); std::visit(lambda_overload{ [&subInfo](const std::monostate&) { subInfo.setStageMask(vk::PipelineStageFlagBits2KHR::eNone);