1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2016 The Khronos Group Inc.
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  *//*!
20  * \file
21  * \brief Synchronization primitive tests with single queue
22  *//*--------------------------------------------------------------------*/
23 
24 #include "vktSynchronizationOperationSingleQueueTests.hpp"
25 #include "vkDefs.hpp"
26 #include "vktTestCase.hpp"
27 #include "vktTestCaseUtil.hpp"
28 #include "vktTestGroupUtil.hpp"
29 #include "vkRef.hpp"
30 #include "vkRefUtil.hpp"
31 #include "vkMemUtil.hpp"
32 #include "vkBarrierUtil.hpp"
33 #include "vkQueryUtil.hpp"
34 #include "vkCmdUtil.hpp"
35 #include "vkTypeUtil.hpp"
36 #include "vkCmdUtil.hpp"
37 #include "deUniquePtr.hpp"
38 #include "tcuTestLog.hpp"
39 #include "vktSynchronizationUtil.hpp"
40 #include "vktSynchronizationOperation.hpp"
41 #include "vktSynchronizationOperationTestData.hpp"
42 #include "vktSynchronizationOperationResources.hpp"
43 
44 namespace vkt
45 {
46 namespace synchronization
47 {
48 namespace
49 {
50 using namespace vk;
51 
52 class BaseTestInstance : public TestInstance
53 {
54 public:
BaseTestInstance(Context & context,const ResourceDescription & resourceDesc,const OperationSupport & writeOp,const OperationSupport & readOp,PipelineCacheData & pipelineCacheData)55 	BaseTestInstance (Context& context, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData)
56 		: TestInstance	(context)
57 		, m_opContext	(context, pipelineCacheData)
58 		, m_resource	(new Resource(m_opContext, resourceDesc, writeOp.getResourceUsageFlags() | readOp.getResourceUsageFlags()))
59 		, m_writeOp		(writeOp.build(m_opContext, *m_resource))
60 		, m_readOp		(readOp.build(m_opContext, *m_resource))
61 	{
62 	}
63 
64 protected:
65 	OperationContext					m_opContext;
66 	const de::UniquePtr<Resource>		m_resource;
67 	const de::UniquePtr<Operation>		m_writeOp;
68 	const de::UniquePtr<Operation>		m_readOp;
69 };
70 
71 class EventTestInstance : public BaseTestInstance
72 {
73 public:
EventTestInstance(Context & context,const ResourceDescription & resourceDesc,const OperationSupport & writeOp,const OperationSupport & readOp,PipelineCacheData & pipelineCacheData)74 	EventTestInstance (Context& context, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData)
75 		: BaseTestInstance		(context, resourceDesc, writeOp, readOp, pipelineCacheData)
76 	{
77 	}
78 
iterate(void)79 	tcu::TestStatus iterate (void)
80 	{
81 		const DeviceInterface&			vk					= m_context.getDeviceInterface();
82 		const VkDevice					device				= m_context.getDevice();
83 		const VkQueue					queue				= m_context.getUniversalQueue();
84 		const deUint32					queueFamilyIndex	= m_context.getUniversalQueueFamilyIndex();
85 		const Unique<VkCommandPool>		cmdPool				(createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex));
86 		const Unique<VkCommandBuffer>	cmdBuffer			(makeCommandBuffer(vk, device, *cmdPool));
87 		const Unique<VkEvent>			event				(createEvent(vk, device));
88 		const SyncInfo					writeSync			= m_writeOp->getSyncInfo();
89 		const SyncInfo					readSync			= m_readOp->getSyncInfo();
90 
91 		beginCommandBuffer(vk, *cmdBuffer);
92 
93 		m_writeOp->recordCommands(*cmdBuffer);
94 		vk.cmdSetEvent(*cmdBuffer, *event, writeSync.stageMask);
95 
96 		if (m_resource->getType() == RESOURCE_TYPE_BUFFER || isIndirectBuffer(m_resource->getType()))
97 		{
98 			const VkBufferMemoryBarrier barrier = makeBufferMemoryBarrier(writeSync.accessMask, readSync.accessMask,
99 				m_resource->getBuffer().handle, m_resource->getBuffer().offset, m_resource->getBuffer().size);
100 			vk.cmdWaitEvents(*cmdBuffer, 1u, &event.get(), writeSync.stageMask, readSync.stageMask, 0u, DE_NULL, 1u, &barrier, 0u, DE_NULL);
101 		}
102 		else if (m_resource->getType() == RESOURCE_TYPE_IMAGE)
103 		{
104 			const VkImageMemoryBarrier barrier =  makeImageMemoryBarrier(writeSync.accessMask, readSync.accessMask,
105 				writeSync.imageLayout, readSync.imageLayout, m_resource->getImage().handle, m_resource->getImage().subresourceRange);
106 			vk.cmdWaitEvents(*cmdBuffer, 1u, &event.get(), writeSync.stageMask, readSync.stageMask, 0u, DE_NULL, 0u, DE_NULL, 1u, &barrier);
107 		}
108 
109 		m_readOp->recordCommands(*cmdBuffer);
110 
111 		endCommandBuffer(vk, *cmdBuffer);
112 		submitCommandsAndWait(vk, device, queue, *cmdBuffer);
113 
114 		{
115 			const Data	expected = m_writeOp->getData();
116 			const Data	actual	 = m_readOp->getData();
117 
118 			if (0 != deMemCmp(expected.data, actual.data, expected.size))
119 				return tcu::TestStatus::fail("Memory contents don't match");
120 		}
121 
122 		return tcu::TestStatus::pass("OK");
123 	}
124 };
125 
126 class BarrierTestInstance : public BaseTestInstance
127 {
128 public:
BarrierTestInstance(Context & context,const ResourceDescription & resourceDesc,const OperationSupport & writeOp,const OperationSupport & readOp,PipelineCacheData & pipelineCacheData)129 	BarrierTestInstance	(Context& context, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData)
130 		: BaseTestInstance		(context, resourceDesc, writeOp, readOp, pipelineCacheData)
131 	{
132 	}
133 
iterate(void)134 	tcu::TestStatus iterate (void)
135 	{
136 		const DeviceInterface&			vk					= m_context.getDeviceInterface();
137 		const VkDevice					device				= m_context.getDevice();
138 		const VkQueue					queue				= m_context.getUniversalQueue();
139 		const deUint32					queueFamilyIndex	= m_context.getUniversalQueueFamilyIndex();
140 		const Unique<VkCommandPool>		cmdPool				(createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex));
141 		const Move<VkCommandBuffer>		cmdBuffer			(makeCommandBuffer(vk, device, *cmdPool));
142 		const SyncInfo					writeSync			= m_writeOp->getSyncInfo();
143 		const SyncInfo					readSync			= m_readOp->getSyncInfo();
144 
145 		beginCommandBuffer(vk, *cmdBuffer);
146 
147 		m_writeOp->recordCommands(*cmdBuffer);
148 
149 		if (m_resource->getType() == RESOURCE_TYPE_BUFFER || isIndirectBuffer(m_resource->getType()))
150 		{
151 			const VkBufferMemoryBarrier barrier = makeBufferMemoryBarrier(writeSync.accessMask, readSync.accessMask,
152 				m_resource->getBuffer().handle, m_resource->getBuffer().offset, m_resource->getBuffer().size);
153 			vk.cmdPipelineBarrier(*cmdBuffer,  writeSync.stageMask, readSync.stageMask, (VkDependencyFlags)0, 0u, (const VkMemoryBarrier*)DE_NULL, 1u, &barrier, 0u, (const VkImageMemoryBarrier*)DE_NULL);
154 		}
155 		else if (m_resource->getType() == RESOURCE_TYPE_IMAGE)
156 		{
157 			const VkImageMemoryBarrier barrier =  makeImageMemoryBarrier(writeSync.accessMask, readSync.accessMask,
158 				writeSync.imageLayout, readSync.imageLayout, m_resource->getImage().handle, m_resource->getImage().subresourceRange);
159 			vk.cmdPipelineBarrier(*cmdBuffer,  writeSync.stageMask, readSync.stageMask, (VkDependencyFlags)0, 0u, (const VkMemoryBarrier*)DE_NULL, 0u, (const VkBufferMemoryBarrier*)DE_NULL, 1u, &barrier);
160 		}
161 
162 		m_readOp->recordCommands(*cmdBuffer);
163 
164 		endCommandBuffer(vk, *cmdBuffer);
165 
166 		submitCommandsAndWait(vk, device, queue, *cmdBuffer);
167 
168 		{
169 			const Data	expected = m_writeOp->getData();
170 			const Data	actual	 = m_readOp->getData();
171 
172 			if (0 != deMemCmp(expected.data, actual.data, expected.size))
173 				return tcu::TestStatus::fail("Memory contents don't match");
174 		}
175 
176 		return tcu::TestStatus::pass("OK");
177 	}
178 };
179 
180 class SemaphoreTestInstance : public BaseTestInstance
181 {
182 public:
SemaphoreTestInstance(Context & context,const ResourceDescription & resourceDesc,const OperationSupport & writeOp,const OperationSupport & readOp,PipelineCacheData & pipelineCacheData)183 	SemaphoreTestInstance (Context& context, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData)
184 		: BaseTestInstance	(context, resourceDesc, writeOp, readOp, pipelineCacheData)
185 	{
186 	}
187 
iterate(void)188 	tcu::TestStatus	iterate (void)
189 	{
190 		enum {WRITE=0, READ, COUNT};
191 		const DeviceInterface&			vk					= m_context.getDeviceInterface();
192 		const VkDevice					device				= m_context.getDevice();
193 		const VkQueue					queue				= m_context.getUniversalQueue();
194 		const deUint32					queueFamilyIndex	= m_context.getUniversalQueueFamilyIndex();
195 		const Unique<VkSemaphore>		semaphore			(createSemaphore (vk, device));
196 		const Unique<VkCommandPool>		cmdPool				(createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex));
197 		const Move<VkCommandBuffer>		ptrCmdBuffer[COUNT]	= {makeCommandBuffer(vk, device, *cmdPool), makeCommandBuffer(vk, device, *cmdPool)};
198 		VkCommandBuffer					cmdBuffers[COUNT]	= {*ptrCmdBuffer[WRITE], *ptrCmdBuffer[READ]};
199 		const VkPipelineStageFlags		stageBits[]			= { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT };
200 		const VkSubmitInfo				submitInfo[2]		=
201 															{
202 																{
203 																	VK_STRUCTURE_TYPE_SUBMIT_INFO,		// VkStructureType			sType;
204 																	DE_NULL,							// const void*				pNext;
205 																	0u,									// deUint32					waitSemaphoreCount;
206 																	DE_NULL,							// const VkSemaphore*		pWaitSemaphores;
207 																	(const VkPipelineStageFlags*)DE_NULL,
208 																	1u,									// deUint32					commandBufferCount;
209 																	&cmdBuffers[WRITE],					// const VkCommandBuffer*	pCommandBuffers;
210 																	1u,									// deUint32					signalSemaphoreCount;
211 																	&semaphore.get(),					// const VkSemaphore*		pSignalSemaphores;
212 																},
213 																{
214 																	VK_STRUCTURE_TYPE_SUBMIT_INFO,		// VkStructureType				sType;
215 																	DE_NULL,							// const void*					pNext;
216 																	1u,									// deUint32						waitSemaphoreCount;
217 																	&semaphore.get(),					// const VkSemaphore*			pWaitSemaphores;
218 																	stageBits,							// const VkPipelineStageFlags*	pWaitDstStageMask;
219 																	1u,									// deUint32						commandBufferCount;
220 																	&cmdBuffers[READ],					// const VkCommandBuffer*		pCommandBuffers;
221 																	0u,									// deUint32						signalSemaphoreCount;
222 																	DE_NULL,							// const VkSemaphore*			pSignalSemaphores;
223 																}
224 															};
225 		const SyncInfo					writeSync			= m_writeOp->getSyncInfo();
226 		const SyncInfo					readSync			= m_readOp->getSyncInfo();
227 
228 		beginCommandBuffer(vk, cmdBuffers[WRITE]);
229 
230 		m_writeOp->recordCommands(cmdBuffers[WRITE]);
231 
232 		if (m_resource->getType() == RESOURCE_TYPE_IMAGE)
233 		{
234 			const VkImageMemoryBarrier barrier =  makeImageMemoryBarrier(writeSync.accessMask, readSync.accessMask,
235 				writeSync.imageLayout, readSync.imageLayout, m_resource->getImage().handle, m_resource->getImage().subresourceRange);
236 			vk.cmdPipelineBarrier(cmdBuffers[WRITE],  writeSync.stageMask, readSync.stageMask, (VkDependencyFlags)0, 0u, (const VkMemoryBarrier*)DE_NULL, 0u, (const VkBufferMemoryBarrier*)DE_NULL, 1u, &barrier);
237 		}
238 		else
239 		{
240 			const VkBufferMemoryBarrier barrier = makeBufferMemoryBarrier(writeSync.accessMask, readSync.accessMask,
241 				m_resource->getBuffer().handle, 0, VK_WHOLE_SIZE);
242 			vk.cmdPipelineBarrier(cmdBuffers[WRITE],  writeSync.stageMask, readSync.stageMask, (VkDependencyFlags)0, 0u, (const VkMemoryBarrier*)DE_NULL, 1u, &barrier, 0u, (const VkImageMemoryBarrier*)DE_NULL);
243 		}
244 
245 		endCommandBuffer(vk, cmdBuffers[WRITE]);
246 
247 		beginCommandBuffer(vk, cmdBuffers[READ]);
248 
249 		m_readOp->recordCommands(cmdBuffers[READ]);
250 
251 		endCommandBuffer(vk, cmdBuffers[READ]);
252 
253 		VK_CHECK(vk.queueSubmit(queue, 2u, submitInfo, DE_NULL));
254 		VK_CHECK(vk.queueWaitIdle(queue));
255 
256 		{
257 			const Data	expected = m_writeOp->getData();
258 			const Data	actual	 = m_readOp->getData();
259 
260 			if (0 != deMemCmp(expected.data, actual.data, expected.size))
261 				return tcu::TestStatus::fail("Memory contents don't match");
262 		}
263 
264 		return tcu::TestStatus::pass("OK");
265 	}
266 };
267 
268 class FenceTestInstance : public BaseTestInstance
269 {
270 public:
FenceTestInstance(Context & context,const ResourceDescription & resourceDesc,const OperationSupport & writeOp,const OperationSupport & readOp,PipelineCacheData & pipelineCacheData)271 	FenceTestInstance (Context& context, const ResourceDescription& resourceDesc, const OperationSupport& writeOp, const OperationSupport& readOp, PipelineCacheData& pipelineCacheData)
272 		: BaseTestInstance	(context, resourceDesc, writeOp, readOp, pipelineCacheData)
273 	{
274 	}
275 
iterate(void)276 	tcu::TestStatus	iterate (void)
277 	{
278 		enum {WRITE=0, READ, COUNT};
279 		const DeviceInterface&			vk					= m_context.getDeviceInterface();
280 		const VkDevice					device				= m_context.getDevice();
281 		const VkQueue					queue				= m_context.getUniversalQueue();
282 		const deUint32					queueFamilyIndex	= m_context.getUniversalQueueFamilyIndex();
283 		const Unique<VkCommandPool>		cmdPool				(createCommandPool(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex));
284 		const Move<VkCommandBuffer>		ptrCmdBuffer[COUNT]	= {makeCommandBuffer(vk, device, *cmdPool), makeCommandBuffer(vk, device, *cmdPool)};
285 		VkCommandBuffer					cmdBuffers[COUNT]	= {*ptrCmdBuffer[WRITE], *ptrCmdBuffer[READ]};
286 		const SyncInfo					writeSync			= m_writeOp->getSyncInfo();
287 		const SyncInfo					readSync			= m_readOp->getSyncInfo();
288 
289 		beginCommandBuffer(vk, cmdBuffers[WRITE]);
290 
291 		m_writeOp->recordCommands(cmdBuffers[WRITE]);
292 
293 		if (m_resource->getType() == RESOURCE_TYPE_IMAGE)
294 		{
295 			const VkImageMemoryBarrier barrier =  makeImageMemoryBarrier(writeSync.accessMask, readSync.accessMask,
296 				writeSync.imageLayout, readSync.imageLayout, m_resource->getImage().handle, m_resource->getImage().subresourceRange);
297 			vk.cmdPipelineBarrier(cmdBuffers[WRITE],  writeSync.stageMask, readSync.stageMask, (VkDependencyFlags)0, 0u, (const VkMemoryBarrier*)DE_NULL, 0u, (const VkBufferMemoryBarrier*)DE_NULL, 1u, &barrier);
298 		}
299 
300 		endCommandBuffer(vk, cmdBuffers[WRITE]);
301 
302 		submitCommandsAndWait(vk, device, queue, cmdBuffers[WRITE]);
303 
304 		beginCommandBuffer(vk, cmdBuffers[READ]);
305 
306 		m_readOp->recordCommands(cmdBuffers[READ]);
307 
308 		endCommandBuffer(vk, cmdBuffers[READ]);
309 
310 		submitCommandsAndWait(vk, device, queue, cmdBuffers[READ]);
311 
312 		{
313 			const Data	expected = m_writeOp->getData();
314 			const Data	actual	 = m_readOp->getData();
315 
316 			if (0 != deMemCmp(expected.data, actual.data, expected.size))
317 				return tcu::TestStatus::fail("Memory contents don't match");
318 		}
319 
320 		return tcu::TestStatus::pass("OK");
321 	}
322 };
323 
324 class SyncTestCase : public TestCase
325 {
326 public:
SyncTestCase(tcu::TestContext & testCtx,const std::string & name,const std::string & description,const SyncPrimitive syncPrimitive,const ResourceDescription resourceDesc,const OperationName writeOp,const OperationName readOp,PipelineCacheData & pipelineCacheData)327 	SyncTestCase	(tcu::TestContext&			testCtx,
328 					 const std::string&			name,
329 					 const std::string&			description,
330 					 const SyncPrimitive		syncPrimitive,
331 					 const ResourceDescription	resourceDesc,
332 					 const OperationName		writeOp,
333 					 const OperationName		readOp,
334 					 PipelineCacheData&			pipelineCacheData)
335 		: TestCase				(testCtx, name, description)
336 		, m_resourceDesc		(resourceDesc)
337 		, m_writeOp				(makeOperationSupport(writeOp, resourceDesc))
338 		, m_readOp				(makeOperationSupport(readOp, resourceDesc))
339 		, m_syncPrimitive		(syncPrimitive)
340 		, m_pipelineCacheData	(pipelineCacheData)
341 	{
342 	}
343 
initPrograms(SourceCollections & programCollection) const344 	void initPrograms (SourceCollections& programCollection) const
345 	{
346 		m_writeOp->initPrograms(programCollection);
347 		m_readOp->initPrograms(programCollection);
348 	}
349 
createInstance(Context & context) const350 	TestInstance* createInstance (Context& context) const
351 	{
352 		switch (m_syncPrimitive)
353 		{
354 			case SYNC_PRIMITIVE_FENCE:
355 				return new FenceTestInstance(context, m_resourceDesc, *m_writeOp, *m_readOp, m_pipelineCacheData);
356 			case SYNC_PRIMITIVE_SEMAPHORE:
357 				return new SemaphoreTestInstance(context, m_resourceDesc, *m_writeOp, *m_readOp, m_pipelineCacheData);
358 			case SYNC_PRIMITIVE_BARRIER:
359 				return new BarrierTestInstance(context, m_resourceDesc, *m_writeOp, *m_readOp, m_pipelineCacheData);
360 			case SYNC_PRIMITIVE_EVENT:
361 				return new EventTestInstance(context, m_resourceDesc, *m_writeOp, *m_readOp, m_pipelineCacheData);
362 		}
363 
364 		DE_ASSERT(0);
365 		return DE_NULL;
366 	}
367 
368 private:
369 	const ResourceDescription				m_resourceDesc;
370 	const de::UniquePtr<OperationSupport>	m_writeOp;
371 	const de::UniquePtr<OperationSupport>	m_readOp;
372 	const SyncPrimitive						m_syncPrimitive;
373 	PipelineCacheData&						m_pipelineCacheData;
374 };
375 
createTests(tcu::TestCaseGroup * group,PipelineCacheData * pipelineCacheData)376 void createTests (tcu::TestCaseGroup* group, PipelineCacheData* pipelineCacheData)
377 {
378 	tcu::TestContext& testCtx = group->getTestContext();
379 
380 	static const struct
381 	{
382 		const char*		name;
383 		SyncPrimitive	syncPrimitive;
384 		int				numOptions;
385 	} groups[] =
386 	{
387 		{ "fence",		SYNC_PRIMITIVE_FENCE,		0, },
388 		{ "semaphore",	SYNC_PRIMITIVE_SEMAPHORE,	0, },
389 		{ "barrier",	SYNC_PRIMITIVE_BARRIER,		1, },
390 		{ "event",		SYNC_PRIMITIVE_EVENT,		1, },
391 	};
392 
393 	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(groups); ++groupNdx)
394 	{
395 		de::MovePtr<tcu::TestCaseGroup> synchGroup (new tcu::TestCaseGroup(testCtx, groups[groupNdx].name, ""));
396 
397 		for (int writeOpNdx = 0; writeOpNdx < DE_LENGTH_OF_ARRAY(s_writeOps); ++writeOpNdx)
398 		for (int readOpNdx = 0; readOpNdx < DE_LENGTH_OF_ARRAY(s_readOps); ++readOpNdx)
399 		{
400 			const OperationName	writeOp		= s_writeOps[writeOpNdx];
401 			const OperationName	readOp		= s_readOps[readOpNdx];
402 			const std::string	opGroupName = getOperationName(writeOp) + "_" + getOperationName(readOp);
403 			bool				empty		= true;
404 
405 			de::MovePtr<tcu::TestCaseGroup> opGroup	(new tcu::TestCaseGroup(testCtx, opGroupName.c_str(), ""));
406 
407 			for (int resourceNdx = 0; resourceNdx < DE_LENGTH_OF_ARRAY(s_resources); ++resourceNdx)
408 			{
409 				const ResourceDescription&	resource	= s_resources[resourceNdx];
410 				std::string					name		= getResourceName(resource);
411 
412 				if (isResourceSupported(writeOp, resource) && isResourceSupported(readOp, resource))
413 				{
414 					opGroup->addChild(new SyncTestCase(testCtx, name, "", groups[groupNdx].syncPrimitive, resource, writeOp, readOp, *pipelineCacheData));
415 					empty = false;
416 				}
417 			}
418 			if (!empty)
419 				synchGroup->addChild(opGroup.release());
420 		}
421 
422 		group->addChild(synchGroup.release());
423 	}
424 }
425 
426 } // anonymous
427 
createSynchronizedOperationSingleQueueTests(tcu::TestContext & testCtx,PipelineCacheData & pipelineCacheData)428 tcu::TestCaseGroup* createSynchronizedOperationSingleQueueTests (tcu::TestContext& testCtx, PipelineCacheData& pipelineCacheData)
429 {
430 	return createTestGroup(testCtx, "single_queue", "Synchronization of a memory-modifying operation", createTests, &pipelineCacheData);
431 }
432 
433 } // synchronization
434 } // vkt
435