/*------------------------------------------------------------------------ * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2016 The Khronos Group Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Synchronization primitive tests with multi queue *//*--------------------------------------------------------------------*/ #include "vktSynchronizationOperationMultiQueueTests.hpp" #include "vktCustomInstancesDevices.hpp" #include "vkDefs.hpp" #include "vktTestCase.hpp" #include "vktTestCaseUtil.hpp" #include "vkRef.hpp" #include "vkRefUtil.hpp" #include "vkMemUtil.hpp" #include "vkBarrierUtil.hpp" #include "vkQueryUtil.hpp" #include "vkTypeUtil.hpp" #include "vkPlatform.hpp" #include "vkCmdUtil.hpp" #include "deRandom.hpp" #include "deUniquePtr.hpp" #include "deSharedPtr.hpp" #include "tcuTestLog.hpp" #include "vktSynchronizationUtil.hpp" #include "vktSynchronizationOperation.hpp" #include "vktSynchronizationOperationTestData.hpp" #include "vktSynchronizationOperationResources.hpp" #include "vktTestGroupUtil.hpp" #include "tcuCommandLine.hpp" #include namespace vkt { namespace synchronization { namespace { using namespace vk; using de::MovePtr; using de::SharedPtr; using de::UniquePtr; using de::SharedPtr; enum QueueType { QUEUETYPE_WRITE, QUEUETYPE_READ }; struct QueuePair { QueuePair (const deUint32 familyWrite, const deUint32 familyRead, const VkQueue write, const VkQueue read) : familyIndexWrite (familyWrite) , familyIndexRead (familyRead) , queueWrite (write) , queueRead (read) {} deUint32 familyIndexWrite; deUint32 familyIndexRead; VkQueue queueWrite; VkQueue queueRead; }; struct Queue { Queue (const deUint32 familyOp, const VkQueue queueOp) : family (familyOp) , queue (queueOp) {} deUint32 family; VkQueue queue; }; bool checkQueueFlags (VkQueueFlags availableFlags, const VkQueueFlags neededFlags) { if ((availableFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) != 0) availableFlags |= VK_QUEUE_TRANSFER_BIT; return (availableFlags & neededFlags) != 0; } class MultiQueues { struct QueueData { VkQueueFlags flags; std::vector queue; }; MultiQueues (const Context& context, SynchronizationType type, bool timelineSemaphore) : m_queueCount (0) { const InstanceInterface& instance = context.getInstanceInterface(); const VkPhysicalDevice physicalDevice = context.getPhysicalDevice(); const std::vector queueFamilyProperties = getPhysicalDeviceQueueFamilyProperties(instance, physicalDevice); for (deUint32 queuePropertiesNdx = 0; queuePropertiesNdx < queueFamilyProperties.size(); ++queuePropertiesNdx) { addQueueIndex(queuePropertiesNdx, std::min(2u, queueFamilyProperties[queuePropertiesNdx].queueCount), queueFamilyProperties[queuePropertiesNdx].queueFlags); } std::vector queueInfos; const float queuePriorities[2] = { 1.0f, 1.0f }; //get max 2 queues from one family for (std::map::iterator it = m_queues.begin(); it!= m_queues.end(); ++it) { const VkDeviceQueueCreateInfo queueInfo = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, //VkStructureType sType; DE_NULL, //const void* pNext; (VkDeviceQueueCreateFlags)0u, //VkDeviceQueueCreateFlags flags; it->first, //deUint32 queueFamilyIndex; static_cast(it->second.queue.size()), //deUint32 queueCount; &queuePriorities[0] //const float* pQueuePriorities; }; queueInfos.push_back(queueInfo); } { VkPhysicalDeviceFeatures2 createPhysicalFeature { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, DE_NULL, context.getDeviceFeatures() }; VkPhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeatures { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, DE_NULL, DE_TRUE }; VkPhysicalDeviceSynchronization2FeaturesKHR synchronization2Features { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR, DE_NULL, DE_TRUE }; void** nextPtr = &createPhysicalFeature.pNext; std::vector deviceExtensions; if (timelineSemaphore) { deviceExtensions.push_back("VK_KHR_timeline_semaphore"); addToChainVulkanStructure(&nextPtr, timelineSemaphoreFeatures); } if (type == SynchronizationType::SYNCHRONIZATION2) { deviceExtensions.push_back("VK_KHR_synchronization2"); addToChainVulkanStructure(&nextPtr, synchronization2Features); } const VkDeviceCreateInfo deviceInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, //VkStructureType sType; &createPhysicalFeature, //const void* pNext; 0u, //VkDeviceCreateFlags flags; static_cast(queueInfos.size()), //deUint32 queueCreateInfoCount; &queueInfos[0], //const VkDeviceQueueCreateInfo* pQueueCreateInfos; 0u, //deUint32 enabledLayerCount; DE_NULL, //const char* const* ppEnabledLayerNames; static_cast(deviceExtensions.size()), //deUint32 enabledExtensionCount; deviceExtensions.empty() ? DE_NULL : &deviceExtensions[0], //const char* const* ppEnabledExtensionNames; DE_NULL //const VkPhysicalDeviceFeatures* pEnabledFeatures; }; m_logicalDevice = createCustomDevice(context.getTestContext().getCommandLine().isValidationEnabled(), context.getPlatformInterface(), context.getInstance(), instance, physicalDevice, &deviceInfo); m_deviceDriver = MovePtr(new DeviceDriver(context.getPlatformInterface(), context.getInstance(), *m_logicalDevice)); m_allocator = MovePtr(new SimpleAllocator(*m_deviceDriver, *m_logicalDevice, getPhysicalDeviceMemoryProperties(instance, physicalDevice))); for (std::map::iterator it = m_queues.begin(); it != m_queues.end(); ++it) for (int queueNdx = 0; queueNdx < static_cast(it->second.queue.size()); ++queueNdx) m_deviceDriver->getDeviceQueue(*m_logicalDevice, it->first, queueNdx, &it->second.queue[queueNdx]); } } void addQueueIndex (const deUint32 queueFamilyIndex, const deUint32 count, const VkQueueFlags flags) { QueueData dataToPush; dataToPush.flags = flags; dataToPush.queue.resize(count); m_queues[queueFamilyIndex] = dataToPush; m_queueCount++; } public: std::vector getQueuesPairs (const VkQueueFlags flagsWrite, const VkQueueFlags flagsRead) const { std::map queuesWrite; std::map queuesRead; std::vector queuesPairs; for (std::map::const_iterator it = m_queues.begin(); it != m_queues.end(); ++it) { const bool writeQueue = checkQueueFlags(it->second.flags, flagsWrite); const bool readQueue = checkQueueFlags(it->second.flags, flagsRead); if (!(writeQueue || readQueue)) continue; if (writeQueue && readQueue) { queuesWrite[it->first] = it->second; queuesRead[it->first] = it->second; } else if (writeQueue) queuesWrite[it->first] = it->second; else if (readQueue) queuesRead[it->first] = it->second; } for (std::map::iterator write = queuesWrite.begin(); write != queuesWrite.end(); ++write) for (std::map::iterator read = queuesRead.begin(); read != queuesRead.end(); ++read) { const int writeSize = static_cast(write->second.queue.size()); const int readSize = static_cast(read->second.queue.size()); for (int writeNdx = 0; writeNdx < writeSize; ++writeNdx) for (int readNdx = 0; readNdx < readSize; ++readNdx) { if (write->second.queue[writeNdx] != read->second.queue[readNdx]) { queuesPairs.push_back(QueuePair(write->first, read->first, write->second.queue[writeNdx], read->second.queue[readNdx])); writeNdx = readNdx = std::max(writeSize, readSize); //exit from the loops } } } if (queuesPairs.empty()) TCU_THROW(NotSupportedError, "Queue not found"); return queuesPairs; } Queue getDefaultQueue(const VkQueueFlags flagsOp) const { for (std::map::const_iterator it = m_queues.begin(); it!= m_queues.end(); ++it) { if (checkQueueFlags(it->second.flags, flagsOp)) return Queue(it->first, it->second.queue[0]); } TCU_THROW(NotSupportedError, "Queue not found"); } Queue getQueue (const deUint32 familyIdx, const deUint32 queueIdx) { return Queue(familyIdx, m_queues[familyIdx].queue[queueIdx]); } VkQueueFlags getQueueFamilyFlags (const deUint32 familyIdx) { return m_queues[familyIdx].flags; } deUint32 queueFamilyCount (const deUint32 familyIdx) { return (deUint32) m_queues[familyIdx].queue.size(); } deUint32 familyCount (void) const { return (deUint32) m_queues.size(); } deUint32 totalQueueCount (void) { deUint32 count = 0; for (deUint32 familyIdx = 0; familyIdx < familyCount(); familyIdx++) { count += queueFamilyCount(familyIdx); } return count; } VkDevice getDevice (void) const { return *m_logicalDevice; } const DeviceInterface& getDeviceInterface (void) const { return *m_deviceDriver; } Allocator& getAllocator (void) { return *m_allocator; } static SharedPtr getInstance(const Context& context, SynchronizationType type, bool timelineSemaphore) { if (!m_multiQueues) m_multiQueues = SharedPtr(new MultiQueues(context, type, timelineSemaphore)); return m_multiQueues; } static void destroy() { m_multiQueues.clear(); } private: Move m_logicalDevice; MovePtr m_deviceDriver; MovePtr m_allocator; std::map m_queues; deUint32 m_queueCount; static SharedPtr m_multiQueues; }; SharedPtr MultiQueues::m_multiQueues; void createBarrierMultiQueue (SynchronizationWrapperPtr synchronizationWrapper, const VkCommandBuffer& cmdBuffer, const SyncInfo& writeSync, const SyncInfo& readSync, const Resource& resource, const deUint32 writeFamily, const deUint32 readFamily, const VkSharingMode sharingMode, const bool secondQueue = false) { if (resource.getType() == RESOURCE_TYPE_IMAGE) { VkImageMemoryBarrier2KHR imageMemoryBarrier2 = makeImageMemoryBarrier2( secondQueue ? VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT) : writeSync.stageMask, secondQueue ? 0u : writeSync.accessMask, !secondQueue ? VkPipelineStageFlags(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT) : readSync.stageMask, !secondQueue ? 0u : readSync.accessMask, writeSync.imageLayout, readSync.imageLayout, resource.getImage().handle, resource.getImage().subresourceRange ); if (writeFamily != readFamily && VK_SHARING_MODE_EXCLUSIVE == sharingMode) { imageMemoryBarrier2.srcQueueFamilyIndex = writeFamily; imageMemoryBarrier2.dstQueueFamilyIndex = readFamily; VkDependencyInfoKHR dependencyInfo = makeCommonDependencyInfo(DE_NULL, DE_NULL, &imageMemoryBarrier2); synchronizationWrapper->cmdPipelineBarrier(cmdBuffer, &dependencyInfo); } else if (!secondQueue) { VkDependencyInfoKHR dependencyInfo = makeCommonDependencyInfo(DE_NULL, DE_NULL, &imageMemoryBarrier2); synchronizationWrapper->cmdPipelineBarrier(cmdBuffer, &dependencyInfo); } } else { VkBufferMemoryBarrier2KHR bufferMemoryBarrier2 = makeBufferMemoryBarrier2( secondQueue ? VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT) : writeSync.stageMask, secondQueue ? 0u : writeSync.accessMask, !secondQueue ? VkPipelineStageFlags(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT) : readSync.stageMask, !secondQueue ? 0u : readSync.accessMask, resource.getBuffer().handle, resource.getBuffer().offset, resource.getBuffer().size ); if (writeFamily != readFamily && VK_SHARING_MODE_EXCLUSIVE == sharingMode) { bufferMemoryBarrier2.srcQueueFamilyIndex = writeFamily; bufferMemoryBarrier2.dstQueueFamilyIndex = readFamily; } VkDependencyInfoKHR dependencyInfo = makeCommonDependencyInfo(DE_NULL, &bufferMemoryBarrier2); synchronizationWrapper->cmdPipelineBarrier(cmdBuffer, &dependencyInfo); } } class BaseTestInstance : public TestInstance { public: BaseTestInstance (Context& context, SynchronizationType type, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData, bool timelineSemaphore) : TestInstance (context) , m_type (type) , m_queues (MultiQueues::getInstance(context, type, timelineSemaphore)) , m_opContext (new OperationContext(context, type, m_queues->getDeviceInterface(), m_queues->getDevice(), m_queues->getAllocator(), pipelineCacheData)) , m_resourceDesc (resourceDesc) , m_writeOp (writeOp) , m_readOp (readOp) { } protected: const SynchronizationType m_type; const SharedPtr m_queues; const UniquePtr m_opContext; const ResourceDescription m_resourceDesc; const OperationSupport& m_writeOp; const OperationSupport& m_readOp; }; class BinarySemaphoreTestInstance : public BaseTestInstance { public: BinarySemaphoreTestInstance (Context& context, SynchronizationType type, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData, const VkSharingMode sharingMode) : BaseTestInstance (context, type, resourceDesc, writeOp, readOp, pipelineCacheData, false) , m_sharingMode (sharingMode) { } tcu::TestStatus iterate (void) { const DeviceInterface& vk = m_opContext->getDeviceInterface(); const VkDevice device = m_opContext->getDevice(); const std::vector queuePairs = m_queues->getQueuesPairs(m_writeOp.getQueueFlags(*m_opContext), m_readOp.getQueueFlags(*m_opContext)); for (deUint32 pairNdx = 0; pairNdx < static_cast(queuePairs.size()); ++pairNdx) { const UniquePtr resource (new Resource(*m_opContext, m_resourceDesc, m_writeOp.getOutResourceUsageFlags() | m_readOp.getInResourceUsageFlags())); const UniquePtr writeOp (m_writeOp.build(*m_opContext, *resource)); const UniquePtr readOp (m_readOp.build (*m_opContext, *resource)); const Move cmdPool[] = { createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queuePairs[pairNdx].familyIndexWrite), createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queuePairs[pairNdx].familyIndexRead) }; const Move ptrCmdBuffer[] = { makeCommandBuffer(vk, device, *cmdPool[QUEUETYPE_WRITE]), makeCommandBuffer(vk, device, *cmdPool[QUEUETYPE_READ]) }; const VkCommandBufferSubmitInfoKHR cmdBufferInfos[] = { makeCommonCommandBufferSubmitInfo(*ptrCmdBuffer[QUEUETYPE_WRITE]), makeCommonCommandBufferSubmitInfo(*ptrCmdBuffer[QUEUETYPE_READ]), }; const Unique semaphore (createSemaphore(vk, device)); VkSemaphoreSubmitInfoKHR waitSemaphoreSubmitInfo = makeCommonSemaphoreSubmitInfo(*semaphore, 0u, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR); VkSemaphoreSubmitInfoKHR signalSemaphoreSubmitInfo = makeCommonSemaphoreSubmitInfo(*semaphore, 0u, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT_KHR); SynchronizationWrapperPtr synchronizationWrapper[] { getSynchronizationWrapper(m_type, vk, DE_FALSE), getSynchronizationWrapper(m_type, vk, DE_FALSE), }; synchronizationWrapper[QUEUETYPE_WRITE]->addSubmitInfo( 0u, DE_NULL, 1u, &cmdBufferInfos[QUEUETYPE_WRITE], 1u, &signalSemaphoreSubmitInfo ); synchronizationWrapper[QUEUETYPE_READ]->addSubmitInfo( 1u, &waitSemaphoreSubmitInfo, 1u, &cmdBufferInfos[QUEUETYPE_READ], 0u, DE_NULL ); const SyncInfo writeSync = writeOp->getOutSyncInfo(); const SyncInfo readSync = readOp->getInSyncInfo(); VkCommandBuffer writeCmdBuffer = cmdBufferInfos[QUEUETYPE_WRITE].commandBuffer; VkCommandBuffer readCmdBuffer = cmdBufferInfos[QUEUETYPE_READ].commandBuffer; beginCommandBuffer (vk, writeCmdBuffer); writeOp->recordCommands (writeCmdBuffer); createBarrierMultiQueue (synchronizationWrapper[QUEUETYPE_WRITE], writeCmdBuffer, writeSync, readSync, *resource, queuePairs[pairNdx].familyIndexWrite, queuePairs[pairNdx].familyIndexRead, m_sharingMode); endCommandBuffer (vk, writeCmdBuffer); beginCommandBuffer (vk, readCmdBuffer); createBarrierMultiQueue (synchronizationWrapper[QUEUETYPE_READ], readCmdBuffer, writeSync, readSync, *resource, queuePairs[pairNdx].familyIndexWrite, queuePairs[pairNdx].familyIndexRead, m_sharingMode, true); readOp->recordCommands (readCmdBuffer); endCommandBuffer (vk, readCmdBuffer); VK_CHECK(synchronizationWrapper[QUEUETYPE_WRITE]->queueSubmit(queuePairs[pairNdx].queueWrite, DE_NULL)); VK_CHECK(synchronizationWrapper[QUEUETYPE_READ]->queueSubmit(queuePairs[pairNdx].queueRead, DE_NULL)); VK_CHECK(vk.queueWaitIdle(queuePairs[pairNdx].queueWrite)); VK_CHECK(vk.queueWaitIdle(queuePairs[pairNdx].queueRead)); { const Data expected = writeOp->getData(); const Data actual = readOp->getData(); if (isIndirectBuffer(m_resourceDesc.type)) { const deUint32 expectedValue = reinterpret_cast(expected.data)[0]; const deUint32 actualValue = reinterpret_cast(actual.data)[0]; if (actualValue < expectedValue) return tcu::TestStatus::fail("Counter value is smaller than expected"); } else { if (0 != deMemCmp(expected.data, actual.data, expected.size)) return tcu::TestStatus::fail("Memory contents don't match"); } } } return tcu::TestStatus::pass("OK"); } private: const VkSharingMode m_sharingMode; }; template inline SharedPtr > makeVkSharedPtr (Move move) { return SharedPtr >(new Move(move)); } class TimelineSemaphoreTestInstance : public BaseTestInstance { public: TimelineSemaphoreTestInstance (Context& context, SynchronizationType type, const ResourceDescription& resourceDesc, const SharedPtr& writeOp, const SharedPtr& readOp, PipelineCacheData& pipelineCacheData, const VkSharingMode sharingMode) : BaseTestInstance (context, type, resourceDesc, *writeOp, *readOp, pipelineCacheData, true) , m_sharingMode (sharingMode) { deUint32 maxQueues = 0; std::vector queueFamilies; if (m_queues->totalQueueCount() < 2) TCU_THROW(NotSupportedError, "Not enough queues"); for (deUint32 familyNdx = 0; familyNdx < m_queues->familyCount(); familyNdx++) { maxQueues = std::max(m_queues->queueFamilyCount(familyNdx), maxQueues); queueFamilies.push_back(familyNdx); } // Create a chain of operations copying data from one resource // to another across at least every single queue of the system // at least once. Each of the operation will be executing with // a dependency on the previous using timeline points. m_opSupports.push_back(writeOp); m_opQueues.push_back(m_queues->getDefaultQueue(writeOp->getQueueFlags(*m_opContext))); for (deUint32 queueIdx = 0; queueIdx < maxQueues; queueIdx++) { for (deUint32 familyIdx = 0; familyIdx < m_queues->familyCount(); familyIdx++) { for (deUint32 copyOpIdx = 0; copyOpIdx < DE_LENGTH_OF_ARRAY(s_copyOps); copyOpIdx++) { if (isResourceSupported(s_copyOps[copyOpIdx], resourceDesc)) { SharedPtr opSupport (makeOperationSupport(s_copyOps[copyOpIdx], m_resourceDesc).release()); if (!checkQueueFlags(opSupport->getQueueFlags(*m_opContext), m_queues->getQueueFamilyFlags(familyIdx))) continue; m_opSupports.push_back(opSupport); m_opQueues.push_back(m_queues->getQueue(familyIdx, queueIdx % m_queues->queueFamilyCount(familyIdx))); break; } } } } m_opSupports.push_back(readOp); m_opQueues.push_back(m_queues->getDefaultQueue(readOp->getQueueFlags(*m_opContext))); // Now create the resources with the usage associated to the // operation performed on the resource. for (deUint32 opIdx = 0; opIdx < (m_opSupports.size() - 1); opIdx++) { deUint32 usage = m_opSupports[opIdx]->getOutResourceUsageFlags() | m_opSupports[opIdx + 1]->getInResourceUsageFlags(); m_resources.push_back(SharedPtr(new Resource(*m_opContext, m_resourceDesc, usage, m_sharingMode, queueFamilies))); } // Finally create the operations using the resources. m_ops.push_back(SharedPtr(m_opSupports[0]->build(*m_opContext, *m_resources[0]).release())); for (deUint32 opIdx = 1; opIdx < (m_opSupports.size() - 1); opIdx++) m_ops.push_back(SharedPtr(m_opSupports[opIdx]->build(*m_opContext, *m_resources[opIdx - 1], *m_resources[opIdx]).release())); m_ops.push_back(SharedPtr(m_opSupports[m_opSupports.size() - 1]->build(*m_opContext, *m_resources.back()).release())); } tcu::TestStatus iterate (void) { const DeviceInterface& vk = m_opContext->getDeviceInterface(); const VkDevice device = m_opContext->getDevice(); de::Random rng (1234); const Unique semaphore (createSemaphoreType(vk, device, VK_SEMAPHORE_TYPE_TIMELINE_KHR)); std::vector > > cmdPools; std::vector > > ptrCmdBuffers; std::vector cmdBufferInfos; std::vector timelineValues; cmdPools.resize(m_queues->familyCount()); for (deUint32 familyIdx = 0; familyIdx < m_queues->familyCount(); familyIdx++) cmdPools[familyIdx] = makeVkSharedPtr(createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, familyIdx)); ptrCmdBuffers.resize(m_ops.size()); cmdBufferInfos.resize(m_ops.size()); for (deUint32 opIdx = 0; opIdx < m_ops.size(); opIdx++) { deUint64 increment = 1 + rng.getUint8(); ptrCmdBuffers[opIdx] = makeVkSharedPtr(makeCommandBuffer(vk, device, **cmdPools[m_opQueues[opIdx].family])); cmdBufferInfos[opIdx] = makeCommonCommandBufferSubmitInfo(**ptrCmdBuffers[opIdx]); timelineValues.push_back(timelineValues.empty() ? increment : (timelineValues.back() + increment)); } for (deUint32 opIdx = 0; opIdx < m_ops.size(); opIdx++) { VkCommandBuffer cmdBuffer = cmdBufferInfos[opIdx].commandBuffer; VkSemaphoreSubmitInfoKHR waitSemaphoreSubmitInfo = makeCommonSemaphoreSubmitInfo(*semaphore, (opIdx == 0 ? 0u : timelineValues[opIdx - 1]), VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR); VkSemaphoreSubmitInfoKHR signalSemaphoreSubmitInfo = makeCommonSemaphoreSubmitInfo(*semaphore, timelineValues[opIdx], VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT_KHR); SynchronizationWrapperPtr synchronizationWrapper = getSynchronizationWrapper(m_type, vk, DE_TRUE); synchronizationWrapper->addSubmitInfo( opIdx == 0 ? 0u : 1u, &waitSemaphoreSubmitInfo, 1u, &cmdBufferInfos[opIdx], 1u, &signalSemaphoreSubmitInfo, opIdx == 0 ? DE_FALSE : DE_TRUE, DE_TRUE ); beginCommandBuffer(vk, cmdBuffer); if (opIdx > 0) { const SyncInfo writeSync = m_ops[opIdx - 1]->getOutSyncInfo(); const SyncInfo readSync = m_ops[opIdx]->getInSyncInfo(); const Resource& resource = *m_resources[opIdx - 1].get(); createBarrierMultiQueue(synchronizationWrapper, cmdBuffer, writeSync, readSync, resource, m_opQueues[opIdx - 1].family, m_opQueues[opIdx].family, m_sharingMode, true); } m_ops[opIdx]->recordCommands(cmdBuffer); if (opIdx < (m_ops.size() - 1)) { const SyncInfo writeSync = m_ops[opIdx]->getOutSyncInfo(); const SyncInfo readSync = m_ops[opIdx + 1]->getInSyncInfo(); const Resource& resource = *m_resources[opIdx].get(); createBarrierMultiQueue(synchronizationWrapper, cmdBuffer, writeSync, readSync, resource, m_opQueues[opIdx].family, m_opQueues[opIdx + 1].family, m_sharingMode); } endCommandBuffer(vk, cmdBuffer); VK_CHECK(synchronizationWrapper->queueSubmit(m_opQueues[opIdx].queue, DE_NULL)); } VK_CHECK(vk.queueWaitIdle(m_opQueues.back().queue)); { const Data expected = m_ops.front()->getData(); const Data actual = m_ops.back()->getData(); if (isIndirectBuffer(m_resourceDesc.type)) { const deUint32 expectedValue = reinterpret_cast(expected.data)[0]; const deUint32 actualValue = reinterpret_cast(actual.data)[0]; if (actualValue < expectedValue) return tcu::TestStatus::fail("Counter value is smaller than expected"); } else { if (0 != deMemCmp(expected.data, actual.data, expected.size)) return tcu::TestStatus::fail("Memory contents don't match"); } } // Make the validation layers happy. for (deUint32 opIdx = 0; opIdx < m_opQueues.size(); opIdx++) VK_CHECK(vk.queueWaitIdle(m_opQueues[opIdx].queue)); return tcu::TestStatus::pass("OK"); } private: const VkSharingMode m_sharingMode; std::vector > m_opSupports; std::vector > m_ops; std::vector > m_resources; std::vector m_opQueues; }; class FenceTestInstance : public BaseTestInstance { public: FenceTestInstance (Context& context, SynchronizationType type, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData, const VkSharingMode sharingMode) : BaseTestInstance (context, type, resourceDesc, writeOp, readOp, pipelineCacheData, false) , m_sharingMode (sharingMode) { } tcu::TestStatus iterate (void) { const DeviceInterface& vk = m_opContext->getDeviceInterface(); const VkDevice device = m_opContext->getDevice(); const std::vector queuePairs = m_queues->getQueuesPairs(m_writeOp.getQueueFlags(*m_opContext), m_readOp.getQueueFlags(*m_opContext)); for (deUint32 pairNdx = 0; pairNdx < static_cast(queuePairs.size()); ++pairNdx) { const UniquePtr resource (new Resource(*m_opContext, m_resourceDesc, m_writeOp.getOutResourceUsageFlags() | m_readOp.getInResourceUsageFlags())); const UniquePtr writeOp (m_writeOp.build(*m_opContext, *resource)); const UniquePtr readOp (m_readOp.build(*m_opContext, *resource)); const Move cmdPool[] { createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queuePairs[pairNdx].familyIndexWrite), createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queuePairs[pairNdx].familyIndexRead) }; const Move ptrCmdBuffer[] { makeCommandBuffer(vk, device, *cmdPool[QUEUETYPE_WRITE]), makeCommandBuffer(vk, device, *cmdPool[QUEUETYPE_READ]) }; const VkCommandBufferSubmitInfoKHR cmdBufferInfos[] { makeCommonCommandBufferSubmitInfo(*ptrCmdBuffer[QUEUETYPE_WRITE]), makeCommonCommandBufferSubmitInfo(*ptrCmdBuffer[QUEUETYPE_READ]) }; SynchronizationWrapperPtr synchronizationWrapper[] { getSynchronizationWrapper(m_type, vk, DE_FALSE), getSynchronizationWrapper(m_type, vk, DE_FALSE), }; const SyncInfo writeSync = writeOp->getOutSyncInfo(); const SyncInfo readSync = readOp->getInSyncInfo(); VkCommandBuffer writeCmdBuffer = cmdBufferInfos[QUEUETYPE_WRITE].commandBuffer; VkCommandBuffer readCmdBuffer = cmdBufferInfos[QUEUETYPE_READ].commandBuffer; beginCommandBuffer (vk, writeCmdBuffer); writeOp->recordCommands (writeCmdBuffer); createBarrierMultiQueue (synchronizationWrapper[QUEUETYPE_WRITE], writeCmdBuffer, writeSync, readSync, *resource, queuePairs[pairNdx].familyIndexWrite, queuePairs[pairNdx].familyIndexRead, m_sharingMode); endCommandBuffer (vk, writeCmdBuffer); submitCommandsAndWait (synchronizationWrapper[QUEUETYPE_WRITE], vk, device, queuePairs[pairNdx].queueWrite, writeCmdBuffer); beginCommandBuffer (vk, readCmdBuffer); createBarrierMultiQueue (synchronizationWrapper[QUEUETYPE_READ], readCmdBuffer, writeSync, readSync, *resource, queuePairs[pairNdx].familyIndexWrite, queuePairs[pairNdx].familyIndexRead, m_sharingMode, true); readOp->recordCommands (readCmdBuffer); endCommandBuffer (vk, readCmdBuffer); submitCommandsAndWait(synchronizationWrapper[QUEUETYPE_READ], vk, device, queuePairs[pairNdx].queueRead, readCmdBuffer); { const Data expected = writeOp->getData(); const Data actual = readOp->getData(); if (isIndirectBuffer(m_resourceDesc.type)) { const deUint32 expectedValue = reinterpret_cast(expected.data)[0]; const deUint32 actualValue = reinterpret_cast(actual.data)[0]; if (actualValue < expectedValue) return tcu::TestStatus::fail("Counter value is smaller than expected"); } else { if (0 != deMemCmp(expected.data, actual.data, expected.size)) return tcu::TestStatus::fail("Memory contents don't match"); } } } return tcu::TestStatus::pass("OK"); } private: const VkSharingMode m_sharingMode; }; class BaseTestCase : public TestCase { public: BaseTestCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description, SynchronizationType type, const SyncPrimitive syncPrimitive, const ResourceDescription resourceDesc, const OperationName writeOp, const OperationName readOp, const VkSharingMode sharingMode, PipelineCacheData& pipelineCacheData) : TestCase (testCtx, name, description) , m_type (type) , m_resourceDesc (resourceDesc) , m_writeOp (makeOperationSupport(writeOp, resourceDesc).release()) , m_readOp (makeOperationSupport(readOp, resourceDesc).release()) , m_syncPrimitive (syncPrimitive) , m_sharingMode (sharingMode) , m_pipelineCacheData (pipelineCacheData) { } void initPrograms (SourceCollections& programCollection) const { m_writeOp->initPrograms(programCollection); m_readOp->initPrograms(programCollection); if (m_syncPrimitive == SYNC_PRIMITIVE_TIMELINE_SEMAPHORE) { for (deUint32 copyOpNdx = 0; copyOpNdx < DE_LENGTH_OF_ARRAY(s_copyOps); copyOpNdx++) { if (isResourceSupported(s_copyOps[copyOpNdx], m_resourceDesc)) makeOperationSupport(s_copyOps[copyOpNdx], m_resourceDesc)->initPrograms(programCollection); } } } void checkSupport(Context& context) const { if (m_type == SynchronizationType::SYNCHRONIZATION2) context.requireDeviceFunctionality("VK_KHR_synchronization2"); if (m_syncPrimitive == SYNC_PRIMITIVE_TIMELINE_SEMAPHORE) context.requireDeviceFunctionality("VK_KHR_timeline_semaphore"); const InstanceInterface& instance = context.getInstanceInterface(); const VkPhysicalDevice physicalDevice = context.getPhysicalDevice(); const std::vector queueFamilyProperties = getPhysicalDeviceQueueFamilyProperties(instance, physicalDevice); if (m_sharingMode == VK_SHARING_MODE_CONCURRENT && queueFamilyProperties.size() < 2) TCU_THROW(NotSupportedError, "Concurrent requires more than 1 queue family"); if (!context.getTimelineSemaphoreFeatures().timelineSemaphore) TCU_THROW(NotSupportedError, "Timeline semaphore not supported"); if (m_resourceDesc.type == RESOURCE_TYPE_IMAGE) { VkImageFormatProperties imageFormatProperties; const deUint32 usage = m_writeOp->getOutResourceUsageFlags() | m_readOp->getInResourceUsageFlags(); const VkResult formatResult = instance.getPhysicalDeviceImageFormatProperties(physicalDevice, m_resourceDesc.imageFormat, m_resourceDesc.imageType, VK_IMAGE_TILING_OPTIMAL, usage, (VkImageCreateFlags)0, &imageFormatProperties); if (formatResult != VK_SUCCESS) TCU_THROW(NotSupportedError, "Image format is not supported"); if ((imageFormatProperties.sampleCounts & m_resourceDesc.imageSamples) != m_resourceDesc.imageSamples) TCU_THROW(NotSupportedError, "Requested sample count is not supported"); } } TestInstance* createInstance (Context& context) const { switch (m_syncPrimitive) { case SYNC_PRIMITIVE_FENCE: return new FenceTestInstance(context, m_type, m_resourceDesc, *m_writeOp, *m_readOp, m_pipelineCacheData, m_sharingMode); case SYNC_PRIMITIVE_BINARY_SEMAPHORE: return new BinarySemaphoreTestInstance(context, m_type, m_resourceDesc, *m_writeOp, *m_readOp, m_pipelineCacheData, m_sharingMode); case SYNC_PRIMITIVE_TIMELINE_SEMAPHORE: return new TimelineSemaphoreTestInstance(context, m_type, m_resourceDesc, m_writeOp, m_readOp, m_pipelineCacheData, m_sharingMode); default: DE_ASSERT(0); return DE_NULL; } } private: const SynchronizationType m_type; const ResourceDescription m_resourceDesc; const SharedPtr m_writeOp; const SharedPtr m_readOp; const SyncPrimitive m_syncPrimitive; const VkSharingMode m_sharingMode; PipelineCacheData& m_pipelineCacheData; }; struct TestData { SynchronizationType type; PipelineCacheData* pipelineCacheData; }; void createTests (tcu::TestCaseGroup* group, TestData data) { tcu::TestContext& testCtx = group->getTestContext(); static const struct { const char* name; SyncPrimitive syncPrimitive; int numOptions; } groups[] = { { "fence", SYNC_PRIMITIVE_FENCE, 1 }, { "binary_semaphore", SYNC_PRIMITIVE_BINARY_SEMAPHORE, 1 }, { "timeline_semaphore", SYNC_PRIMITIVE_TIMELINE_SEMAPHORE, 1 } }; for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(groups); ++groupNdx) { MovePtr synchGroup (new tcu::TestCaseGroup(testCtx, groups[groupNdx].name, "")); for (int writeOpNdx = 0; writeOpNdx < DE_LENGTH_OF_ARRAY(s_writeOps); ++writeOpNdx) for (int readOpNdx = 0; readOpNdx < DE_LENGTH_OF_ARRAY(s_readOps); ++readOpNdx) { const OperationName writeOp = s_writeOps[writeOpNdx]; const OperationName readOp = s_readOps[readOpNdx]; const std::string opGroupName = getOperationName(writeOp) + "_" + getOperationName(readOp); bool empty = true; MovePtr opGroup (new tcu::TestCaseGroup(testCtx, opGroupName.c_str(), "")); for (int optionNdx = 0; optionNdx <= groups[groupNdx].numOptions; ++optionNdx) for (int resourceNdx = 0; resourceNdx < DE_LENGTH_OF_ARRAY(s_resources); ++resourceNdx) { const ResourceDescription& resource = s_resources[resourceNdx]; if (isResourceSupported(writeOp, resource) && isResourceSupported(readOp, resource)) { std::string name = getResourceName(resource); VkSharingMode sharingMode = VK_SHARING_MODE_EXCLUSIVE; // queue family sharing mode used for resource if (optionNdx) { name += "_concurrent"; sharingMode = VK_SHARING_MODE_CONCURRENT; } else name += "_exclusive"; opGroup->addChild(new BaseTestCase(testCtx, name, "", data.type, groups[groupNdx].syncPrimitive, resource, writeOp, readOp, sharingMode, *data.pipelineCacheData)); empty = false; } } if (!empty) synchGroup->addChild(opGroup.release()); } group->addChild(synchGroup.release()); } } void cleanupGroup (tcu::TestCaseGroup* group, TestData data) { DE_UNREF(group); DE_UNREF(data.pipelineCacheData); // Destroy singleton object MultiQueues::destroy(); } } // anonymous tcu::TestCaseGroup* createSynchronizedOperationMultiQueueTests (tcu::TestContext& testCtx, SynchronizationType type, PipelineCacheData& pipelineCacheData) { TestData data { type, &pipelineCacheData }; return createTestGroup(testCtx, "multi_queue", "Synchronization of a memory-modifying operation", createTests, data, cleanupGroup); } } // synchronization } // vkt