blob: 84f25fb3a43df76055922a3af68c371f0bf6d9a1 [file] [log] [blame]
/*------------------------------------------------------------------------
* 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 <set>
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<VkQueue> 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<VkQueueFamilyProperties> 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<VkDeviceQueueCreateInfo> queueInfos;
const float queuePriorities[2] = { 1.0f, 1.0f }; //get max 2 queues from one family
for (std::map<deUint32, QueueData>::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<deUint32>(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<const char*> 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<deUint32>(queueInfos.size()), //deUint32 queueCreateInfoCount;
&queueInfos[0], //const VkDeviceQueueCreateInfo* pQueueCreateInfos;
0u, //deUint32 enabledLayerCount;
DE_NULL, //const char* const* ppEnabledLayerNames;
static_cast<deUint32>(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<DeviceDriver>(new DeviceDriver(context.getPlatformInterface(), context.getInstance(), *m_logicalDevice));
m_allocator = MovePtr<Allocator>(new SimpleAllocator(*m_deviceDriver, *m_logicalDevice, getPhysicalDeviceMemoryProperties(instance, physicalDevice)));
for (std::map<deUint32, QueueData>::iterator it = m_queues.begin(); it != m_queues.end(); ++it)
for (int queueNdx = 0; queueNdx < static_cast<int>(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<QueuePair> getQueuesPairs (const VkQueueFlags flagsWrite, const VkQueueFlags flagsRead) const
{
std::map<deUint32, QueueData> queuesWrite;
std::map<deUint32, QueueData> queuesRead;
std::vector<QueuePair> queuesPairs;
for (std::map<deUint32, QueueData>::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<deUint32, QueueData>::iterator write = queuesWrite.begin(); write != queuesWrite.end(); ++write)
for (std::map<deUint32, QueueData>::iterator read = queuesRead.begin(); read != queuesRead.end(); ++read)
{
const int writeSize = static_cast<int>(write->second.queue.size());
const int readSize = static_cast<int>(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<deUint32, QueueData>::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<MultiQueues> getInstance(const Context& context, SynchronizationType type, bool timelineSemaphore)
{
if (!m_multiQueues)
m_multiQueues = SharedPtr<MultiQueues>(new MultiQueues(context, type, timelineSemaphore));
return m_multiQueues;
}
static void destroy()
{
m_multiQueues.clear();
}
private:
Move<VkDevice> m_logicalDevice;
MovePtr<DeviceDriver> m_deviceDriver;
MovePtr<Allocator> m_allocator;
std::map<deUint32, QueueData> m_queues;
deUint32 m_queueCount;
static SharedPtr<MultiQueues> m_multiQueues;
};
SharedPtr<MultiQueues> 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<MultiQueues> m_queues;
const UniquePtr<OperationContext> 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<QueuePair> queuePairs = m_queues->getQueuesPairs(m_writeOp.getQueueFlags(*m_opContext), m_readOp.getQueueFlags(*m_opContext));
for (deUint32 pairNdx = 0; pairNdx < static_cast<deUint32>(queuePairs.size()); ++pairNdx)
{
const UniquePtr<Resource> resource (new Resource(*m_opContext, m_resourceDesc, m_writeOp.getOutResourceUsageFlags() | m_readOp.getInResourceUsageFlags()));
const UniquePtr<Operation> writeOp (m_writeOp.build(*m_opContext, *resource));
const UniquePtr<Operation> readOp (m_readOp.build (*m_opContext, *resource));
const Move<VkCommandPool> 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<VkCommandBuffer> 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<VkSemaphore> 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<const deUint32*>(expected.data)[0];
const deUint32 actualValue = reinterpret_cast<const deUint32*>(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<typename T>
inline SharedPtr<Move<T> > makeVkSharedPtr (Move<T> move)
{
return SharedPtr<Move<T> >(new Move<T>(move));
}
class TimelineSemaphoreTestInstance : public BaseTestInstance
{
public:
TimelineSemaphoreTestInstance (Context& context, SynchronizationType type, const ResourceDescription& resourceDesc, const SharedPtr<OperationSupport>& writeOp, const SharedPtr<OperationSupport>& readOp, PipelineCacheData& pipelineCacheData, const VkSharingMode sharingMode)
: BaseTestInstance (context, type, resourceDesc, *writeOp, *readOp, pipelineCacheData, true)
, m_sharingMode (sharingMode)
{
deUint32 maxQueues = 0;
std::vector<deUint32> 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<OperationSupport> 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<Resource>(new Resource(*m_opContext, m_resourceDesc, usage, m_sharingMode, queueFamilies)));
}
// Finally create the operations using the resources.
m_ops.push_back(SharedPtr<Operation>(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<Operation>(m_opSupports[opIdx]->build(*m_opContext, *m_resources[opIdx - 1], *m_resources[opIdx]).release()));
m_ops.push_back(SharedPtr<Operation>(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<VkSemaphore> semaphore (createSemaphoreType(vk, device, VK_SEMAPHORE_TYPE_TIMELINE_KHR));
std::vector<SharedPtr<Move<VkCommandPool> > > cmdPools;
std::vector<SharedPtr<Move<VkCommandBuffer> > > ptrCmdBuffers;
std::vector<VkCommandBufferSubmitInfoKHR> cmdBufferInfos;
std::vector<deUint64> 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<const deUint32*>(expected.data)[0];
const deUint32 actualValue = reinterpret_cast<const deUint32*>(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<SharedPtr<OperationSupport> > m_opSupports;
std::vector<SharedPtr<Operation> > m_ops;
std::vector<SharedPtr<Resource> > m_resources;
std::vector<Queue> 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<QueuePair> queuePairs = m_queues->getQueuesPairs(m_writeOp.getQueueFlags(*m_opContext), m_readOp.getQueueFlags(*m_opContext));
for (deUint32 pairNdx = 0; pairNdx < static_cast<deUint32>(queuePairs.size()); ++pairNdx)
{
const UniquePtr<Resource> resource (new Resource(*m_opContext, m_resourceDesc, m_writeOp.getOutResourceUsageFlags() | m_readOp.getInResourceUsageFlags()));
const UniquePtr<Operation> writeOp (m_writeOp.build(*m_opContext, *resource));
const UniquePtr<Operation> readOp (m_readOp.build(*m_opContext, *resource));
const Move<VkCommandPool> 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<VkCommandBuffer> 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<const deUint32*>(expected.data)[0];
const deUint32 actualValue = reinterpret_cast<const deUint32*>(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<VkQueueFamilyProperties> 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 (m_syncPrimitive == SYNC_PRIMITIVE_TIMELINE_SEMAPHORE &&
!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<OperationSupport> m_writeOp;
const SharedPtr<OperationSupport> 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<tcu::TestCaseGroup> 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<tcu::TestCaseGroup> 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