Vulkan SC-NvSciSync API Usage Examples

Following is example code to demonstrate practical usage of VK_NV_external_sci_sync2. The example is edited for brevity, to focus on the interoperability of Vulkan SC and NvSciSync, is incomplete for the complete Vulkan SC creation, and ignores the NvSciIPC process. Your application must check each VkResult and NvSciError and API function returns, and handle any error values.

VkFence


VkPhysicalDevice physdev = VK_NULL_HANDLE;
VkDevice         dev = VK_NULL_HANDLE;
// Create VkPhysicalDevice
// ...

VkDeviceCreateInfo deviceInfo {};
// Enable VK_NV_external_sci_sync2 extension
std::vector<const char*> extNamesDev;    extNamesDev.push_back(VK_NV_EXTERNAL_SCI_SYNC_2_EXTENSION_NAME);
deviceInfo.enabledExtensionCount = (uint32_t)extNamesDev.size();
deviceInfo.ppEnabledExtensionNames = extNamesDev.data();
// Initialize other parts of deviceInfo
// ...
vkCreateDevice(physdev[0], &deviceInfo, nullptr, &dev);

// Initial Phase
NvSciSyncModule   scisyncModule {nullptr};
NvSciSyncAttrList attrListWaiter;
NvSciSyncModuleOpen(&scisyncModule);

// Waiter creates NvSciSyncAttrList
NvSciSyncAttrListCreate(scisyncModule, &attrListWaiter);
VkSciSyncAttributesInfoNV vkSciSyncAttributesInfo = {
.sType = VK_STRUCTURE_TYPE_SCI_SYNC_ATTRIBUTES_INFO_NV,
       .pNext = nullptr,
       .clientType = VK_SCI_SYNC_CLIENT_TYPE_WAITER_NV,
       .primitiveType = VK_SCI_SYNC_PRIMITIVE_TYPE_FENCE_NV
};
vkGetPhysicalDeviceSciSyncAttributesNV(
     physdev, &vkSciSyncAttributesInfo, *attrListWaiter);

// Signaler creates NvSciSyncAttrList
NvSciSyncAttrList attrListSignaler;
VkSciSyncAttributesInfoNV vkSciSyncAttributesInfo = {
.sType = VK_STRUCTURE_TYPE_SCI_SYNC_ATTRIBUTES_INFO_NV,
       .pNext = nullptr,
       .clientType = VK_SCI_SYNC_CLIENT_TYPE_SIGNALER_NV,
       .primitiveType = VK_SCI_SYNC_PRIMITIVE_TYPE_FENCE_NV
};
vkGetPhysicalDeviceSciSyncAttributesNV(
     physdev, &vkSciSyncAttributesInfo, *attrListWaiter);

// Using NvSciIPC to import/export NvSciSyncAttrList
// ... 
// Signaler reconcile the NvSciSyncAttrList
NvSciSyncAttrList reconciledList;
NvSciSyncAttrList unreconciledList[2] = {attrListWaiter, attrListSignaler};
NvSciSyncAttrList conflictList;
NvSciSyncAttrListReconcile(unreconciledList, 2, &reconciledList,&conflictList);


// Signaler allocate NvSciSyncObj and import to VkFence
NvSciSyncObj signalerSciSyncObj = nullptr;
VkFence signalerVkFence = VK_NULL_HANDLE;
NvSciSyncObjAlloc(reconciledList, &signalerSciSyncObj);
VkFenceCreateInfo fenceCreateInfo = {};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
vkCreateFence(dev, &fenceCreateInfo, nullptr, &signalerVkFence);
VkImportFenceSciSyncInfoNV importSciSyncInfo = {
.sType = VK_STRUCTURE_TYPE_IMPORT_FENCE_SCI_SYNC_INFO_NV;
.pNext = nullptr;
.fence = signalerVkFence;
.handleType = VK_EXTERNAL_FENCE_HANDLE_TYPE_SCI_SYNC_OBJ_BIT_NV
.handle = signalerSciSyncObj;
}; 
vkImportFenceSciSyncObjNV(dev, &importSciSyncInfo);
// Export signalerSciSyncObj via NvSciIPC
// ...


NvSciSyncObj waiterSciSyncObj;
// Waiter Import waiterSciSyncObj via NvSciIPC
// ...

// Import to VkFence
VkFence waiterVkFence;
VkFenceCreateInfo fenceCreateInfo = {};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
vkCreateFence(dev, &fenceCreateInfo, nullptr, &signalerVkFence);VkImportFenceSciSyncInfoNV importSciSyncInfo = {
.sType = VK_STRUCTURE_TYPE_IMPORT_FENCE_SCI_SYNC_INFO_NV;
.pNext = nullptr;
.fence = waiterVkFence;
.handleType = VK_EXTERNAL_FENCE_HANDLE_TYPE_SCI_SYNC_OBJ_BIT_NV
.handle = waiterSciSyncObj;
 }; 
vkImportFenceSciSyncObjNV(dev, &importSciSyncInfo);

// Run-Time Phase
// Signaler signals VkFence
VkSubmitInfo submitInfo = {};
vkQueueSubmit(vksc_queue, 1, &submitInfo, signalerVkFence);
VkFenceGetSciSyncInfoNV getSciSyncInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_GET_SCI_SYNC_INFO_NV;
.pNext = nullptr;
.fence = signalerVkFence,
.handleType = VK_EXTERNAL_FENCE_HANDLE_TYPE_SCI_SYNC_FENCE_BIT_NV,
};
NvSciSyncFence signalerSciSyncFence {NvSciSyncFenceInitializer};
vkGetFenceSciSyncFenceNV(dev, &getSciSyncInfo, &signalerSciSyncFence);
// Export signalerSciSyncFence via NvSciIPC
// ...

// Waiter waits VkFence
NvSciSyncFence waiterSciSyncFence {NvSciSyncFenceInitializer};
// Waiter imports waiterSciSyncFence via NvSciIPC
// ...
VkImportFenceSciSyncInfoNV importSciSyncInfo = {
.sType = VK_STRUCTURE_TYPE_IMPORT_FENCE_SCI_SYNC_INFO_NV;
.pNext = nullptr;
.fence = waiterVkFence;
.handleType = VK_EXTERNAL_FENCE_HANDLE_TYPE_SCI_SYNC_FENCE_BIT_NV
.handle = waiterSciSyncFence;
};
vkImportFenceSciSyncFenceNV(dev, &importSciSyncInfo);
uint64_t timeout = 1000000LLU;
vkWaitForFences(dev, 1, waiterVkFence, timeout);

// Deinit Phase
NvSciSyncAttrListFree(reconciledList);
NvSciSyncAttrListFree(conflictList);
NvSciSyncAttrListFree(attrListWaiter);
NvSciSyncAttrListFree(attrListSignaler);
NvSciSyncModuleClose(scisyncModule);
vkDestroyFence(waiterVkFence);
vkDestroyFence(signalerVkFence);
vkDestroyDevice(dev, nullptr);

VkSemaphore

VkPhysicalDevice physdev = VK_NULL_HANDLE;
VkDevice         dev = VK_NULL_HANDLE;
// Create VkPhysicalDevice
// ...

VkDeviceCreateInfo deviceInfo {};
// Enable VK_NV_external_sci_sync2 extension
std::vector<const char*> extNamesDev;    extNamesDev.push_back(VK_NV_EXTERNAL_SCI_SYNC_2_EXTENSION_NAME);
deviceInfo.enabledExtensionCount = (uint32_t)extNamesDev.size();
deviceInfo.ppEnabledExtensionNames = extNamesDev.data();
// Initialize other parts of deviceInfo
// ...
vkCreateDevice(physdev[0], &deviceInfo, nullptr, &dev);

// Initial Phase
NvSciSyncModule   scisyncModule {nullptr};
NvSciSyncAttrList attrListWaiter;
NvSciSyncModuleOpen(&scisyncModule);

// Waiter creates NvSciSyncAttrList
NvSciSyncAttrListCreate(scisyncModule, &attrListWaiter);
VkSciSyncAttributesInfoNV vkSciSyncAttributesInfo = {
.sType = VK_STRUCTURE_TYPE_SCI_SYNC_ATTRIBUTES_INFO_NV,
       .pNext = nullptr,
       .clientType = VK_SCI_SYNC_CLIENT_TYPE_WAITER_NV,
       .primitiveType = VK_SCI_SYNC_PRIMITIVE_TYPE_SEMAPHORE_NV
};
vkGetPhysicalDeviceSciSyncAttributesNV(
     physdev, &vkSciSyncAttributesInfo, *attrListWaiter);

// Signaler creates NvSciSyncAttrList
NvSciSyncAttrList attrListSignaler;
VkSciSyncAttributesInfoNV vkSciSyncAttributesInfo = {
.sType = VK_STRUCTURE_TYPE_SCI_SYNC_ATTRIBUTES_INFO_NV,
       .pNext = nullptr,
       .clientType = VK_SCI_SYNC_CLIENT_TYPE_SIGNALER_NV,
       .primitiveType = VK_SCI_SYNC_PRIMITIVE_TYPE_SEMAPHORE_NV
};
vkGetPhysicalDeviceSciSyncAttributesNV(
     physdev, &vkSciSyncAttributesInfo, *attrListWaiter);

// Using NvSciIPC to import/export NvSciSyncAttrList
// ... 
// Signaler reconcile the NvSciSyncAttrList
NvSciSyncAttrList reconciledList;
NvSciSyncAttrList unreconciledList[2] = {attrListWaiter, attrListSignaler};
NvSciSyncAttrList conflictList;
NvSciSyncAttrListReconcile(unreconciledList, 2, &reconciledList,&conflictList);


// Signaler allocate NvSciSyncObj and import to VkSemaphoreSciSyncPoolNV
NvSciSyncObj signalerSciSyncObj;
NvSciSyncObjAlloc(reconciledList, &signalerSciSyncObj);
VkSemaphoreSciSyncPoolCreateInfoNV signalerInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SCI_SYNC_POOL_CREATE_INFO_NV;
.pNext = nullptr;
.handle = signalerSciSyncObj
};
VkSemaphoreSciSyncPoolNV signalerSemPool = VK_NULL_HANDLE;
vkCreateSemaphoreSciSyncPoolNV(dev, &signalerInfo, nullptr, &signalerSemPool);

// Export signalerSciSyncObj via NvSciIPC
// ...


NvSciSyncObj waiterSciSyncObj;
// Waiter Import waiterSciSyncObj via NvSciIPC
// ...
VkSemaphoreSciSyncPoolCreateInfoNV waiterInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SCI_SYNC_POOL_CREATE_INFO_NV;
.pNext = nullptr;
.handle = waiterSciSyncObj
};
VkSemaphoreSciSyncPoolNV waiterSemPool = VK_NULL_HANDLE;
vkCreateSemaphoreSciSyncPoolNV(dev, &waiterInfo, nullptr, &signalerSemPool);

// Run-time Phase
// Signaler signals VkSemaphore
uint32_t const fenceId = 0;
uint32_t signalValue = 0;

// Application tracks and maintains the signal value
signalValue = signalVal + 1U;
NvSciSyncFence signalerFence{};
NvSciSyncFenceUpdateFence(signalerSciSyncObj, fenceId, signalValue, &signalerFence);


// Retrieve VkSemaphore from the VkSemaphoreSciSyncPoolNV providing 
// NvSciSyncFence
VkSemaphoreSciSyncCreateInfoNV semaphoreSciSyncInfo = {
    .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SCI_SYNC_CREATE_INFO_NV,
    .pNext = nullptr,
    .semaphorePool = signalerSemPool,
    .pFence = signalerFence
};

// Only timeline semaphore supports NvSciSync interops
VkSemaphoreTypeCreateInfo sempahoreTypeInfo = {
    .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
    .pNext = &semaphoreSciSyncInfo,
    .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE,
    .initialValue = 0     // initialValue must be 0 with NvSciSyncObj
};

VkSemaphoreCreateInfo semaphoreCreateInfo = {
    .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
    .pNext = &sempahoreTypeInfo,
    .flags = 0, // reserved bit, must be zero
};
VkSemaphore signalerSemaphore;
vkCreateSemaphore(dev, &semaphoreCreateInfo, nullptr, &signalerSemaphore);

// GPU Signal
if (GPU_SIGNAL) {

    VkTimelineSemaphoreSubmitInfo semaphoreInfo = {
        .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
        .pNext = nullptr,
        .waitSemaphoreValueCount = 0,
        .pWaitSemaphoreValues = nullptr,
        .signalSemaphoreValueCount = 1,
        .pSignalSemaphoreValues = &signalValue
    };
    VkSubmitInfo submitInfo = {
        .sType =  VK_STRUCTURE_TYPE_SUBMIT_INFO,
        .pNext = &semaphoreInfo,
        .waitSemaphoreCount = 0,
        .pWaitSemaphores = nullptr,
        .pWaitDstStageMask = nullptr,
        .commandBufferCount = 0,
        .pCommandBuffers = nullptr,
        .signalSemaphoreCount = 1,
        .pSignalSemaphores = &signalerSemaphore,
    };
    vkQueueSubmit(m_queue, 1, &submitInfo, nullptr);
}

else if (CPU_SIGNAL) {
    VkSemaphoreSignalInfo signalInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO,
        .pNext = nullptr,
        .semaphore = signalerSemaphore,
        .value = signalValue
    };
    vkSignalSemaphore(dev, &signalInfo);
}
// Export signalerFence via NvSciIPC
// ...
// Recycle the VkSemaphore back to the pool
vkDestroySemaphore(dev, signalerSemaphore, nullptr);

// Waiter waits VkSemaphore
NvSciSyncFence waiterFence{};
// Import waiterFence via NvSciIPC
// ...

uint32_t const fenceId = 0;
uint32_t waitValue = 0;
NvSciSyncFenceExtractFence(&waiterFence, &fenceId, &waitValue);
// Retreive VkSemaphore from the VkSemaphoreSciSyncPoolNV providing 
// NvSciSyncFence
VkSemaphoreSciSyncCreateInfoNV semaphoreSciSyncInfo = {
    .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SCI_SYNC_CREATE_INFO_NV,
    .pNext = nullptr,
    .semaphorePool = waiterSemPool,
    .pFence = waiterFence
};

// Only timeline semaphore supports NvSciSync interops
VkSemaphoreTypeCreateInfo sempahoreTypeInfo = {
    .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
    .pNext = &semaphoreSciSyncInfo,
    .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE,
    .initialValue = 0     // initialValue must be 0 with NvSciSyncObj
};
VkSemaphoreCreateInfo semaphoreCreateInfo = {
    .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
    .pNext = &sempahoreTypeInfo,
    .flags = 0, // reserved bit, must be zero
};
VkSemaphore waiterSemaphore;
vkCreateSemaphore(dev, &semaphoreCreateInfo, nullptr, &waiterSemaphore);
if (GPU_WAIT) {

    VkTimelineSemaphoreSubmitInfo semaphoreInfo = {
        .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
        .pNext = nullptr,
        .waitSemaphoreValueCount = 1,
        .pWaitSemaphoreValues = &waitValue,
        .signalSemaphoreValueCount = 0,
        .pSignalSemaphoreValues = nullptr
    };
    VkSubmitInfo submitInfo = {
        .sType =  VK_STRUCTURE_TYPE_SUBMIT_INFO,
        .pNext = &semaphoreInfo,
        .waitSemaphoreCount = 1,
        .pWaitSemaphores = &waiterSemaphore,
        .pWaitDstStageMask = nullptr,
        .commandBufferCount = 0,
        .pCommandBuffers = nullptr,
        .signalSemaphoreCount = 0,
        .pSignalSemaphores = nullptr,
    };
    vkQueueSubmit(m_queue, 1, &submitInfo, nullptr);
}
else if (CPU_WAIT) {
    VkSemaphoreWaitInfo waitInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO,
        .pNext = nullptr,
        .flags = 0,
        .semaphoreCount = 1,
        .pSemaphores = &waiterSemaphore,
        .pValues = &value
    };
    uint64_t timeout = 1000000LLU;
    vkWaitSemaphores(m_dev, &waitInfo, timeout);
}
// Recycle the VkSemaphore back to the pool
vkDestroySemaphore(dev, vkWaitSemaphores, nullptr);

VkSemaphore on Deterministic Fence

// Skip the Init-time part, assume Deterministic Fence is enabled
// ...

// Run-time Phase
// Signaler signals VkSemaphore
uint32_t const fenceId = 0;
uint32_t signalValue = 0;
while (true) {

    // Application tracks and maintains the signal value, it will always increment 
    // by 1 on the signal value.
    signalValue = signalVal + 1U;
    NvSciSyncFence signalerFence{};
    NvSciSyncFenceUpdateFence(signalerSciSyncObj, fenceId, signalValue, &signalerFence);

    // Retreive VkSemaphore from the VkSemaphoreSciSyncPoolNV providing 
    // NvSciSyncFence
     VkSemaphoreSciSyncCreateInfoNV semaphoreSciSyncInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SCI_SYNC_CREATE_INFO_NV,
        .pNext = nullptr,
        .semaphorePool = signalerSemPool,
        .pFence = signalerFence
    };

    // Only timeline semaphore supports NvSciSync interops
    VkSemaphoreTypeCreateInfo sempahoreTypeInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
        .pNext = &semaphoreSciSyncInfo,
        .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE,
        .initialValue = 0     // initialValue must be 0 with NvSciSyncObj
    };

    VkSemaphoreCreateInfo semaphoreCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        .pNext = &sempahoreTypeInfo,
        .flags = 0, // reserved bit, must be zero
    };
    VkSemaphore signalerSemaphore;
    vkCreateSemaphore(dev, &semaphoreCreateInfo, nullptr, &signalerSemaphore);

    // Only show the GPU Signal path
    VkTimelineSemaphoreSubmitInfo semaphoreInfo = {
        .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
        .pNext = nullptr,
        .waitSemaphoreValueCount = 0,
        .pWaitSemaphoreValues = nullptr,
        .signalSemaphoreValueCount = 1,
        .pSignalSemaphoreValues = &signalValue
    };
    VkSubmitInfo submitInfo = {
        .sType =  VK_STRUCTURE_TYPE_SUBMIT_INFO,
        .pNext = &semaphoreInfo,
        .waitSemaphoreCount = 0,
        .pWaitSemaphores = nullptr,
        .pWaitDstStageMask = nullptr,
        .commandBufferCount = 0,
        .pCommandBuffers = nullptr,
        .signalSemaphoreCount = 1,
        .pSignalSemaphores = &signalerSemaphore,
    };
    vkQueueSubmit(m_queue, 1, &submitInfo, nullptr);
    // Recycle the VkSemaphore back to the pool
    vkDestroySemaphore(dev, signalerSemaphore, nullptr);
}

// Waiter waits VkSemaphore
uint32_t const fenceId = 0;
uint32_t waitValue = 0;
while(true) {
    // Applications constructs a NvSciSyncFence themselves insead of importing from

    // NvSciIPC

    waitValue = waitValue + 1U;
    NvSciSyncFence waiterFence{};
    NvSciSyncFenceUpdateFence(waiterSciSyncObj, fenceId, waitValue, &waiterFence);
    // Retreive VkSemaphore from the VkSemaphoreSciSyncPoolNV providing 
    // NvSciSyncFence
    VkSemaphoreSciSyncCreateInfoNV semaphoreSciSyncInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SCI_SYNC_CREATE_INFO_NV,
        .pNext = nullptr,
        .semaphorePool = waiterSemPool,
        .pFence = waiterFence
    };

    // Only timeline semaphore supports NvSciSync interops
    VkSemaphoreTypeCreateInfo sempahoreTypeInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
        .pNext = &semaphoreSciSyncInfo,
        .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE,
        .initialValue = 0     // initialValue must be 0 with NvSciSyncObj
    };
    VkSemaphoreCreateInfo semaphoreCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        .pNext = &sempahoreTypeInfo,
        .flags = 0, // reserved bit, must be zero
    };
    VkSemaphore waiterSemaphore;
    vkCreateSemaphore(dev, &semaphoreCreateInfo, nullptr, &waiterSemaphore);

    VkTimelineSemaphoreSubmitInfo semaphoreInfo = {
        .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
        .pNext = nullptr,
        .waitSemaphoreValueCount = 1,
        .pWaitSemaphoreValues = &waitValue,
        .signalSemaphoreValueCount = 0,
        .pSignalSemaphoreValues = nullptr
    };
    VkSubmitInfo submitInfo = {
        .sType =  VK_STRUCTURE_TYPE_SUBMIT_INFO,
        .pNext = &semaphoreInfo,
        .waitSemaphoreCount = 1,
        .pWaitSemaphores = &waiterSemaphore,
        .pWaitDstStageMask = nullptr,
        .commandBufferCount = 0,
        .pCommandBuffers = nullptr,
        .signalSemaphoreCount = 0,
        .pSignalSemaphores = nullptr,
    };
    vkQueueSubmit(m_queue, 1, &submitInfo, nullptr);
    // Recycle the VkSemaphore back to the pool
    vkDestroySemaphore(dev, vkWaitSemaphores, nullptr);
}