test(dualgpu): Replace synchronization primitives with shareable ones

This will increase latency a bunch, both due to not being on-device only, but also due to CPU overhead in building and submitting the command buffer. This is just a temporary test and not ready for production anyways
This commit is contained in:
PancakeTAS 2026-02-10 13:38:22 +01:00
parent 997bc665f7
commit d9fcbcd10e
9 changed files with 98 additions and 87 deletions

View file

@ -79,17 +79,8 @@ namespace lsfgvk::backend {
/// - false: VK_FORMAT_R8G8B8A8_UNORM /// - false: VK_FORMAT_R8G8B8A8_UNORM
/// - true: VK_FORMAT_R16G16B16A16_SFLOAT /// - true: VK_FORMAT_R16G16B16A16_SFLOAT
/// ///
/// The application and library must keep track of the frame index. When the next frame
/// is ready, signal the syncFd with one increment (with the first trigger being 1).
/// Each generated frame will increment the semaphore by one:
/// - Application signals 1 -> Start generating with (curr, next) source images
/// - Library signals 1 -> First frame between (curr, next) is ready
/// - Library signals N -> N-th frame between (curr, next) is ready
/// - Application signals N+1 -> Start generating with (next, curr) source images
///
/// @param sourceFds Pair of file descriptors for the source images alternated between. /// @param sourceFds Pair of file descriptors for the source images alternated between.
/// @param destFds Vector with file descriptors to import output images from. /// @param destFds Vector with file descriptors to import output images from.
/// @param syncFd File descriptor for the timeline semaphore used for synchronization.
/// @param width Width of the images. /// @param width Width of the images.
/// @param height Height of the images. /// @param height Height of the images.
/// @param hdr Whether the images are HDR. /// @param hdr Whether the images are HDR.
@ -101,7 +92,6 @@ namespace lsfgvk::backend {
Context& openContext( Context& openContext(
std::pair<int, int> sourceFds, std::pair<int, int> sourceFds,
const std::vector<int>& destFds, const std::vector<int>& destFds,
int syncFd,
uint32_t width, uint32_t height, uint32_t width, uint32_t height,
bool hdr, float flow, bool perf bool hdr, float flow, bool perf
); );
@ -110,9 +100,11 @@ namespace lsfgvk::backend {
/// Schedule a new set of generated frames. /// Schedule a new set of generated frames.
/// ///
/// @param context Context to use. /// @param context Context to use.
/// @param waitFd File descriptor to wait on before starting frame generation.
/// @param syncFds Vector of file descriptors to emplace, signalled when generated frames are ready.
/// @throws backend::error on failure /// @throws backend::error on failure
/// ///
void scheduleFrames(Context& context); void scheduleFrames(Context& context, int waitFd, std::vector<int>& syncFds);
/// ///
/// Close a frame generation context /// Close a frame generation context

View file

@ -11,6 +11,7 @@
#include "lsfg-vk-common/vulkan/command_buffer.hpp" #include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/fence.hpp" #include "lsfg-vk-common/vulkan/fence.hpp"
#include "lsfg-vk-common/vulkan/image.hpp" #include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/semaphore.hpp"
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp" #include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp" #include "lsfg-vk-common/vulkan/vulkan.hpp"
#include "shaderchains/alpha0.hpp" #include "shaderchains/alpha0.hpp"
@ -99,18 +100,18 @@ namespace lsfgvk::backend {
/// create a context /// create a context
/// (see lsfg-vk documentation) /// (see lsfg-vk documentation)
ContextImpl(const InstanceImpl& instance, ContextImpl(const InstanceImpl& instance,
std::pair<int, int> sourceFds, const std::vector<int>& destFds, int syncFd, std::pair<int, int> sourceFds, const std::vector<int>& destFds,
VkExtent2D extent, bool hdr, float flow, bool perf); VkExtent2D extent, bool hdr, float flow, bool perf);
/// schedule frames /// schedule frames
/// (see lsfg-vk documentation) /// (see lsfg-vk documentation)
void scheduleFrames(); void scheduleFrames(int waitFd, std::vector<int>& syncFds);
private: private:
std::pair<vk::Image, vk::Image> sourceImages; std::pair<vk::Image, vk::Image> sourceImages;
std::vector<vk::Image> destImages; std::vector<vk::Image> destImages;
vk::Image blackImage; vk::Image blackImage;
vk::TimelineSemaphore syncSemaphore; // imported ls::lazy<vk::Semaphore> syncSemaphore; // imported
vk::TimelineSemaphore prepassSemaphore; vk::TimelineSemaphore prepassSemaphore;
size_t idx{1}; size_t idx{1};
size_t fidx{0}; // real frame index size_t fidx{0}; // real frame index
@ -126,6 +127,8 @@ namespace lsfgvk::backend {
Beta0 beta0; Beta0 beta0;
Beta1 beta1; Beta1 beta1;
struct Pass { struct Pass {
ls::lazy<vk::Semaphore> sync2Semaphore; // imported
std::vector<Gamma0> gamma0; std::vector<Gamma0> gamma0;
std::vector<Gamma1> gamma1; std::vector<Gamma1> gamma1;
@ -274,11 +277,11 @@ InstanceImpl::InstanceImpl(vk::PhysicalDeviceSelector selectPhysicalDevice,
} }
Context& Instance::openContext(std::pair<int, int> sourceFds, const std::vector<int>& destFds, Context& Instance::openContext(std::pair<int, int> sourceFds, const std::vector<int>& destFds,
int syncFd, uint32_t width, uint32_t height, uint32_t width, uint32_t height,
bool hdr, float flow, bool perf) { bool hdr, float flow, bool perf) {
const VkExtent2D extent{ width, height }; const VkExtent2D extent{ width, height };
return *this->m_contexts.emplace_back(std::make_unique<ContextImpl>(*this->m_impl, return *this->m_contexts.emplace_back(std::make_unique<ContextImpl>(*this->m_impl,
sourceFds, destFds, syncFd, sourceFds, destFds,
extent, hdr, flow, perf extent, hdr, flow, perf
)).get(); )).get();
} }
@ -326,14 +329,6 @@ namespace {
throw backend::error("Unable to create black image", e); throw backend::error("Unable to create black image", e);
} }
} }
/// import timeline semaphore
vk::TimelineSemaphore importTimelineSemaphore(const vk::Vulkan& vk, int syncFd) {
try {
return{vk, 0, syncFd};
} catch (const std::exception& e) {
throw backend::error("Unable to import timeline semaphore", e);
}
}
/// create prepass semaphores /// create prepass semaphores
vk::TimelineSemaphore createPrepassSemaphore(const vk::Vulkan& vk) { vk::TimelineSemaphore createPrepassSemaphore(const vk::Vulkan& vk) {
try { try {
@ -400,14 +395,13 @@ namespace {
} }
ContextImpl::ContextImpl(const InstanceImpl& instance, ContextImpl::ContextImpl(const InstanceImpl& instance,
std::pair<int, int> sourceFds, const std::vector<int>& destFds, int syncFd, std::pair<int, int> sourceFds, const std::vector<int>& destFds,
VkExtent2D extent, bool hdr, float flow, bool perf) : VkExtent2D extent, bool hdr, float flow, bool perf) :
sourceImages(importImages(instance.getVulkan(), sourceFds, sourceImages(importImages(instance.getVulkan(), sourceFds,
extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM)), extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM)),
destImages(importImages(instance.getVulkan(), destFds, destImages(importImages(instance.getVulkan(), destFds,
extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM)), extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM)),
blackImage(createBlackImage(instance.getVulkan())), blackImage(createBlackImage(instance.getVulkan())),
syncSemaphore(importTimelineSemaphore(instance.getVulkan(), syncFd)),
prepassSemaphore(createPrepassSemaphore(instance.getVulkan())), prepassSemaphore(createPrepassSemaphore(instance.getVulkan())),
cmdbufs(createCommandBuffers(instance.getVulkan(), destFds.size() + 1)), cmdbufs(createCommandBuffers(instance.getVulkan(), destFds.size() + 1)),
cmdbufFence(instance.getVulkan()), cmdbufFence(instance.getVulkan()),
@ -549,7 +543,7 @@ ContextImpl::ContextImpl(const InstanceImpl& instance,
cmdbuf.submit(ctx.vk); // wait for completion cmdbuf.submit(ctx.vk); // wait for completion
} }
void Instance::scheduleFrames(Context& context) { void Instance::scheduleFrames(Context& context, int waitFd, std::vector<int>& syncFds) {
#ifdef LSFGVK_TESTING_RENDERDOC #ifdef LSFGVK_TESTING_RENDERDOC
const auto& impl = this->m_impl; const auto& impl = this->m_impl;
if (impl->getRenderDocAPI()) { if (impl->getRenderDocAPI()) {
@ -559,7 +553,7 @@ void Instance::scheduleFrames(Context& context) {
} }
#endif #endif
try { try {
context.scheduleFrames(); context.scheduleFrames(waitFd, syncFds);
} catch (const std::exception& e) { } catch (const std::exception& e) {
throw backend::error("Unable to schedule frames", e); throw backend::error("Unable to schedule frames", e);
} }
@ -573,12 +567,17 @@ void Instance::scheduleFrames(Context& context) {
#endif #endif
} }
void Context::scheduleFrames() { void Context::scheduleFrames(int waitFd, std::vector<int>& syncFds) {
// wait for previous pre-pass to complete // wait for previous pre-pass to complete
if (this->fidx && !this->cmdbufFence.wait(this->ctx.vk)) if (this->fidx && !this->cmdbufFence.wait(this->ctx.vk))
throw backend::error("Timeout waiting for previous frame to complete"); throw backend::error("Timeout waiting for previous frame to complete");
this->cmdbufFence.reset(this->ctx.vk); this->cmdbufFence.reset(this->ctx.vk);
// import sync semaphore
this->syncSemaphore.emplace(ctx.vk, waitFd);
for (size_t i = 0; i < this->destImages.size(); ++i)
this->passes.at(i).sync2Semaphore.emplace(ctx.vk, std::nullopt, true);
// schedule pre-pass // schedule pre-pass
const auto& cmdbuf = this->cmdbufs.at(0); const auto& cmdbuf = this->cmdbufs.at(0);
cmdbuf.begin(ctx.vk); cmdbuf.begin(ctx.vk);
@ -593,13 +592,15 @@ void Context::scheduleFrames() {
cmdbuf.end(ctx.vk); cmdbuf.end(ctx.vk);
cmdbuf.submit(this->ctx.vk, cmdbuf.submit(this->ctx.vk,
{}, this->syncSemaphore.handle(), this->idx, { this->syncSemaphore->handle() }, VK_NULL_HANDLE, 0,
{}, this->prepassSemaphore.handle(), this->idx {}, this->prepassSemaphore.handle(), this->idx
); );
this->idx++; this->idx++;
// schedule main passes // schedule main passes
syncFds.clear();
for (size_t i = 0; i < this->destImages.size(); i++) { for (size_t i = 0; i < this->destImages.size(); i++) {
const auto& cmdbuf = this->cmdbufs.at(i + 1); const auto& cmdbuf = this->cmdbufs.at(i + 1);
cmdbuf.begin(ctx.vk); cmdbuf.begin(ctx.vk);
@ -618,9 +619,11 @@ void Context::scheduleFrames() {
cmdbuf.end(ctx.vk); cmdbuf.end(ctx.vk);
cmdbuf.submit(this->ctx.vk, cmdbuf.submit(this->ctx.vk,
{}, this->prepassSemaphore.handle(), this->idx - 1, {}, this->prepassSemaphore.handle(), this->idx - 1,
{}, this->syncSemaphore.handle(), this->idx + i, { pass.sync2Semaphore->handle() }, VK_NULL_HANDLE, 0,
i == this->destImages.size() - 1 ? this->cmdbufFence.handle() : VK_NULL_HANDLE i == this->destImages.size() - 1 ? this->cmdbufFence.handle() : VK_NULL_HANDLE
); );
syncFds.push_back(pass.sync2Semaphore->exportToFd(ctx.vk));
} }
this->idx += this->destImages.size(); this->idx += this->destImages.size();

View file

@ -25,8 +25,9 @@ namespace ls {
/// @throws std::logic_error if value already present /// @throws std::logic_error if value already present
template<typename... Args> template<typename... Args>
T& emplace(Args&&... args) { T& emplace(Args&&... args) {
if (this->opt.has_value()) // FIXME: should not be commented out
throw std::logic_error("lazy: value already present"); // if (this->opt.has_value())
// throw std::logic_error("lazy: value already present");
this->opt.emplace(std::forward<Args>(args)...); this->opt.emplace(std::forward<Args>(args)...);
return *this->opt; return *this->opt;

View file

@ -15,9 +15,18 @@ namespace vk {
public: public:
/// create a semaphore /// create a semaphore
/// @param vk the vulkan instance /// @param vk the vulkan instance
/// @param fd optional file descriptor to import the semaphore from /// @param importFd optional file descriptor to import from
/// @param markExport whether to mark the semaphore as exportable
/// @throws ls::vulkan_error on failure /// @throws ls::vulkan_error on failure
Semaphore(const vk::Vulkan& vk, std::optional<int> fd = std::nullopt); Semaphore(const vk::Vulkan& vk,
std::optional<int> importFd = std::nullopt,
bool markExport = false);
/// export the semaphore to a file descriptor
/// @param vk the vulkan instance
/// @return the exported file descriptor
/// @throws ls::vulkan_error on failure
[[nodiscard]] int exportToFd(const vk::Vulkan& vk) const;
/// get the underlying VkSemaphore handle /// get the underlying VkSemaphore handle
/// @return the VkSemaphore handle /// @return the VkSemaphore handle

View file

@ -13,28 +13,30 @@ using namespace vk;
namespace { namespace {
/// create a semaphore /// create a semaphore
ls::owned_ptr<VkSemaphore> createSemaphore(const vk::Vulkan& vk, std::optional<int> fd) { ls::owned_ptr<VkSemaphore> createSemaphore(const vk::Vulkan& vk,
std::optional<int> importFd, bool markExport) {
VkSemaphore handle{}; VkSemaphore handle{};
const VkExportSemaphoreCreateInfo exportInfo{ const VkExportSemaphoreCreateInfo exportInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT
}; };
const VkSemaphoreCreateInfo semaphoreInfo{ const VkSemaphoreCreateInfo semaphoreInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
.pNext = fd.has_value() ? &exportInfo : nullptr .pNext = (importFd.has_value() || markExport) ? &exportInfo : nullptr,
}; };
auto res = vk.df().CreateSemaphore(vk.dev(), &semaphoreInfo, VK_NULL_HANDLE, &handle); auto res = vk.df().CreateSemaphore(vk.dev(), &semaphoreInfo, VK_NULL_HANDLE, &handle);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkCreateSemaphore() failed"); throw ls::vulkan_error(res, "vkCreateSemaphore() failed");
if (fd.has_value()) { if (importFd.has_value()) {
// import semaphore from fd // import semaphore from fd
const VkImportSemaphoreFdInfoKHR importInfo{ const VkImportSemaphoreFdInfoKHR importInfo{
.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
.semaphore = handle, .semaphore = handle,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT,
.fd = *fd // closes the fd .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
.fd = *importFd // closes the fd
}; };
res = vk.df().ImportSemaphoreFdKHR(vk.dev(), &importInfo); res = vk.df().ImportSemaphoreFdKHR(vk.dev(), &importInfo);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
@ -50,5 +52,20 @@ namespace {
} }
} }
Semaphore::Semaphore(const vk::Vulkan& vk, std::optional<int> fd) Semaphore::Semaphore(const vk::Vulkan& vk,
: semaphore(createSemaphore(vk, fd)) {} std::optional<int> importFd, bool markExport)
: semaphore(createSemaphore(vk, importFd, markExport)) {}
int Semaphore::exportToFd(const vk::Vulkan& vk) const {
int fd{};
const VkSemaphoreGetFdInfoKHR getFdInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
.semaphore = *this->semaphore,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT
};
auto res = vk.df().GetSemaphoreFdKHR(vk.dev(), &getFdInfo, &fd);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkGetSemaphoreFdKHR() failed");
return fd;
}

View file

@ -20,11 +20,11 @@ namespace {
const VkExportSemaphoreCreateInfo exportInfo{ const VkExportSemaphoreCreateInfo exportInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT
}; };
const VkSemaphoreTypeCreateInfo typeInfo{ const VkSemaphoreTypeCreateInfo typeInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
.pNext = ( importFd.has_value() || exportFd.has_value() ) ? &exportInfo : nullptr, .pNext = (importFd.has_value() || exportFd.has_value()) ? &exportInfo : nullptr,
.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE,
.initialValue = initial .initialValue = initial
}; };
@ -41,7 +41,7 @@ namespace {
const VkImportSemaphoreFdInfoKHR importInfo{ const VkImportSemaphoreFdInfoKHR importInfo{
.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
.semaphore = handle, .semaphore = handle,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
.fd = *importFd // closes the fd .fd = *importFd // closes the fd
}; };
res = vk.df().ImportSemaphoreFdKHR(vk.dev(), &importInfo); res = vk.df().ImportSemaphoreFdKHR(vk.dev(), &importInfo);
@ -54,7 +54,7 @@ namespace {
const VkSemaphoreGetFdInfoKHR getFdInfo{ const VkSemaphoreGetFdInfoKHR getFdInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR,
.semaphore = handle, .semaphore = handle,
.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT
}; };
int fd{}; int fd{};
res = vk.df().GetSemaphoreFdKHR(vk.dev(), &getFdInfo, &fd); res = vk.df().GetSemaphoreFdKHR(vk.dev(), &getFdInfo, &fd);

View file

@ -120,31 +120,7 @@ void Root::modifyDeviceCreateInfo(VkDeviceCreateInfo& createInfo,
); );
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data(); createInfo.ppEnabledExtensionNames = extensions.data();
bool isFeatureEnabled = false;
auto* featureInfo = reinterpret_cast<VkBaseInStructure*>(const_cast<void*>(createInfo.pNext));
while (featureInfo) {
if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES) {
auto* features = reinterpret_cast<VkPhysicalDeviceVulkan12Features*>(featureInfo);
features->timelineSemaphore = VK_TRUE;
isFeatureEnabled = true;
} else if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES) {
auto* features = reinterpret_cast<VkPhysicalDeviceTimelineSemaphoreFeatures*>(featureInfo);
features->timelineSemaphore = VK_TRUE;
isFeatureEnabled = true;
}
featureInfo = const_cast<VkBaseInStructure*>(featureInfo->pNext);
}
VkPhysicalDeviceTimelineSemaphoreFeatures timelineFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
.pNext = const_cast<void*>(createInfo.pNext),
.timelineSemaphore = VK_TRUE
};
if (!isFeatureEnabled)
createInfo.pNext = &timelineFeatures;
finish(); finish();
} }

View file

@ -90,13 +90,10 @@ Swapchain::Swapchain(const vk::Vulkan& vk, backend::Instance& backend,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
std::nullopt, &fd); std::nullopt, &fd);
int syncFd{};
this->syncSemaphore.emplace(vk, 0, std::nullopt, &syncFd);
try { try {
this->ctx = ls::owned_ptr<ls::R<backend::Context>>( this->ctx = ls::owned_ptr<ls::R<backend::Context>>(
new ls::R<backend::Context>(backend.openContext( new ls::R<backend::Context>(backend.openContext(
{ sourceFds.at(0), sourceFds.at(1) }, destinationFds, syncFd, { sourceFds.at(0), sourceFds.at(1) }, destinationFds,
extent.width, extent.height, extent.width, extent.height,
hdr, 1.0F / this->profile.flow_scale, this->profile.performance_mode hdr, 1.0F / this->profile.flow_scale, this->profile.performance_mode
)), )),
@ -135,13 +132,6 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
const auto& swapchainImage = this->info.images.at(imageIdx); const auto& swapchainImage = this->info.images.at(imageIdx);
const auto& sourceImage = this->sourceImages.at(this->fidx % 2); const auto& sourceImage = this->sourceImages.at(this->fidx % 2);
// schedule frame generation
try {
this->instance.get().scheduleFrames(this->ctx.get());
} catch (const std::exception& e) {
throw ls::error("failed to schedule frames", e);
}
// update present mode when not using pacing // update present mode when not using pacing
if (this->profile.pacing == ls::Pacing::None) { if (this->profile.pacing == ls::Pacing::None) {
#pragma clang diagnostic push #pragma clang diagnostic push
@ -196,12 +186,29 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
} }
); );
this->syncSemaphore.emplace(vk, std::nullopt, true);
cmdbuf.end(vk); cmdbuf.end(vk);
cmdbuf.submit(vk, cmdbuf.submit(vk,
semaphores, VK_NULL_HANDLE, 0, semaphores, VK_NULL_HANDLE, 0,
{}, this->syncSemaphore->handle(), this->idx++ { this->syncSemaphore->handle() }, VK_NULL_HANDLE, 0
); );
this->idx++;
// schedule frame generation
std::vector<int> waitFds;
try {
this->instance.get().scheduleFrames(
this->ctx.get(),
this->syncSemaphore->exportToFd(vk),
waitFds
);
} catch (const std::exception& e) {
throw ls::error("failed to schedule frames", e);
}
for (size_t i = 0; i < this->destinationImages.size(); i++) { for (size_t i = 0; i < this->destinationImages.size(); i++) {
auto& pcs = this->postCopySemaphores.at(this->idx % this->postCopySemaphores.size()); auto& pcs = this->postCopySemaphores.at(this->idx % this->postCopySemaphores.size());
auto& destinationImage = this->destinationImages.at(i); auto& destinationImage = this->destinationImages.at(i);
@ -250,7 +257,12 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
} }
); );
std::vector<VkSemaphore> waitSemaphores{ pass.acquireSemaphore.handle() }; pass.sync2Semaphore.emplace(vk, waitFds.at(i));
std::vector<VkSemaphore> waitSemaphores{
pass.acquireSemaphore.handle(),
pass.sync2Semaphore->handle()
};
if (i) { // non-first pass if (i) { // non-first pass
const auto& prevPCS = this->postCopySemaphores.at((this->idx - 1) % this->postCopySemaphores.size()); const auto& prevPCS = this->postCopySemaphores.at((this->idx - 1) % this->postCopySemaphores.size());
waitSemaphores.push_back(prevPCS.second.handle()); waitSemaphores.push_back(prevPCS.second.handle());
@ -263,7 +275,7 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
cmdbuf.end(vk); cmdbuf.end(vk);
cmdbuf.submit(vk, cmdbuf.submit(vk,
waitSemaphores, this->syncSemaphore->handle(), this->idx, waitSemaphores, VK_NULL_HANDLE, 0,
signalSemaphores, VK_NULL_HANDLE, 0, signalSemaphores, VK_NULL_HANDLE, 0,
i == this->destinationImages.size() - 1 ? this->renderFence->handle() : VK_NULL_HANDLE i == this->destinationImages.size() - 1 ? this->renderFence->handle() : VK_NULL_HANDLE
); );

View file

@ -61,11 +61,12 @@ namespace lsfgvk::layer {
private: private:
std::vector<vk::Image> sourceImages; std::vector<vk::Image> sourceImages;
std::vector<vk::Image> destinationImages; std::vector<vk::Image> destinationImages;
ls::lazy<vk::TimelineSemaphore> syncSemaphore; ls::lazy<vk::Semaphore> syncSemaphore;
ls::lazy<vk::CommandBuffer> renderCommandBuffer; ls::lazy<vk::CommandBuffer> renderCommandBuffer;
ls::lazy<vk::Fence> renderFence; ls::lazy<vk::Fence> renderFence;
struct RenderPass { struct RenderPass {
ls::lazy<vk::Semaphore> sync2Semaphore;
vk::CommandBuffer commandBuffer; vk::CommandBuffer commandBuffer;
vk::Semaphore acquireSemaphore; vk::Semaphore acquireSemaphore;
}; };