1 /*-------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2017 Google 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 Testing compute shader writing to separate planes of a multiplanar format
22 *//*--------------------------------------------------------------------*/
23
24 #include "vktYCbCrStorageImageWriteTests.hpp"
25 #include "vktTestCaseUtil.hpp"
26 #include "vktTestGroupUtil.hpp"
27 #include "vktYCbCrUtil.hpp"
28 #include "vkBuilderUtil.hpp"
29 #include "vkObjUtil.hpp"
30 #include "vkCmdUtil.hpp"
31 #include "vkBarrierUtil.hpp"
32 #include "vkImageUtil.hpp"
33 #include "tcuTexVerifierUtil.hpp"
34 #include "vkTypeUtil.hpp"
35 #include "vkRefUtil.hpp"
36 #include "vkQueryUtil.hpp"
37 #include "tcuTestLog.hpp"
38 #include <cstdio>
39
40 namespace vkt
41 {
42 namespace ycbcr
43 {
44 namespace
45 {
46
47 using namespace vk;
48
49 struct TestParameters
50 {
51 VkFormat format;
52 tcu::UVec3 size;
53 VkImageCreateFlags flags;
54
TestParametersvkt::ycbcr::__anonf5c6a4150111::TestParameters55 TestParameters(VkFormat format_, const tcu::UVec3 &size_, VkImageCreateFlags flags_)
56 : format(format_)
57 , size(size_)
58 , flags(flags_)
59 {
60 }
61
TestParametersvkt::ycbcr::__anonf5c6a4150111::TestParameters62 TestParameters(void) : format(VK_FORMAT_UNDEFINED), flags(0u)
63 {
64 }
65 };
66
getPlaneCompatibleFormatForWriting(const vk::PlanarFormatDescription & formatInfo,uint32_t planeNdx)67 vk::VkFormat getPlaneCompatibleFormatForWriting(const vk::PlanarFormatDescription &formatInfo, uint32_t planeNdx)
68 {
69 DE_ASSERT(planeNdx < formatInfo.numPlanes);
70 vk::VkFormat result = formatInfo.planes[planeNdx].planeCompatibleFormat;
71
72 // redirect result for some of the YCbCr image formats
73 static const std::pair<vk::VkFormat, vk::VkFormat> ycbcrFormats[] = {
74 {VK_FORMAT_G8B8G8R8_422_UNORM, VK_FORMAT_R8G8B8A8_UNORM},
75 {VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, VK_FORMAT_R16G16B16A16_UNORM},
76 {VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, VK_FORMAT_R16G16B16A16_UNORM},
77 {VK_FORMAT_G16B16G16R16_422_UNORM, VK_FORMAT_R16G16B16A16_UNORM},
78 {VK_FORMAT_B8G8R8G8_422_UNORM, VK_FORMAT_R8G8B8A8_UNORM},
79 {VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, VK_FORMAT_R16G16B16A16_UNORM},
80 {VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, VK_FORMAT_R16G16B16A16_UNORM},
81 {VK_FORMAT_B16G16R16G16_422_UNORM, VK_FORMAT_R16G16B16A16_UNORM}};
82 auto it = std::find_if(std::begin(ycbcrFormats), std::end(ycbcrFormats),
83 [result](const std::pair<vk::VkFormat, vk::VkFormat> &p) { return p.first == result; });
84 if (it != std::end(ycbcrFormats))
85 result = it->second;
86 return result;
87 }
88
checkSupport(Context & context,const TestParameters params)89 void checkSupport(Context &context, const TestParameters params)
90 {
91 const bool disjoint = (params.flags & VK_IMAGE_CREATE_DISJOINT_BIT) != 0;
92 const auto &instInt(context.getInstanceInterface());
93 std::vector<std::string> reqExts;
94
95 if (disjoint)
96 {
97 if (!isCoreDeviceExtension(context.getUsedApiVersion(), "VK_KHR_bind_memory2"))
98 reqExts.push_back("VK_KHR_bind_memory2");
99 if (!isCoreDeviceExtension(context.getUsedApiVersion(), "VK_KHR_get_memory_requirements2"))
100 reqExts.push_back("VK_KHR_get_memory_requirements2");
101 }
102
103 for (const auto &extIter : reqExts)
104 {
105 if (!context.isDeviceFunctionalitySupported(extIter))
106 TCU_THROW(NotSupportedError, (extIter + " is not supported").c_str());
107 }
108
109 {
110 const VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {
111 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, // sType;
112 DE_NULL, // pNext;
113 params.format, // format;
114 VK_IMAGE_TYPE_2D, // type;
115 VK_IMAGE_TILING_OPTIMAL, // tiling;
116 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, // usage;
117 (VkImageCreateFlags)0u // flags
118 };
119
120 VkSamplerYcbcrConversionImageFormatProperties samplerYcbcrConversionImage = {};
121 samplerYcbcrConversionImage.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES;
122 samplerYcbcrConversionImage.pNext = DE_NULL;
123
124 VkImageFormatProperties2 imageFormatProperties = {};
125 imageFormatProperties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
126 imageFormatProperties.pNext = &samplerYcbcrConversionImage;
127
128 VkResult result = instInt.getPhysicalDeviceImageFormatProperties2(context.getPhysicalDevice(), &imageFormatInfo,
129 &imageFormatProperties);
130 if (result == VK_ERROR_FORMAT_NOT_SUPPORTED)
131 TCU_THROW(NotSupportedError, "Format not supported.");
132 VK_CHECK(result);
133
134 // Check for plane compatible format support when the disjoint flag is being used
135 if (disjoint)
136 {
137 const PlanarFormatDescription formatDescription = getPlanarFormatDescription(params.format);
138
139 for (uint32_t channelNdx = 0; channelNdx < 4; ++channelNdx)
140 {
141 if (!formatDescription.hasChannelNdx(channelNdx))
142 continue;
143 uint32_t planeNdx = formatDescription.channels[channelNdx].planeNdx;
144 vk::VkFormat planeCompatibleFormat = getPlaneCompatibleFormatForWriting(formatDescription, planeNdx);
145
146 const VkPhysicalDeviceImageFormatInfo2 planeImageFormatInfo = {
147 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, // sType;
148 DE_NULL, // pNext;
149 planeCompatibleFormat, // format;
150 VK_IMAGE_TYPE_2D, // type;
151 VK_IMAGE_TILING_OPTIMAL, // tiling;
152 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, // usage;
153 (VkImageCreateFlags)0u // flags
154 };
155
156 VkResult planesResult = instInt.getPhysicalDeviceImageFormatProperties2(
157 context.getPhysicalDevice(), &planeImageFormatInfo, &imageFormatProperties);
158 if (planesResult == VK_ERROR_FORMAT_NOT_SUPPORTED)
159 TCU_THROW(NotSupportedError, "Plane compatibile format not supported.");
160 VK_CHECK(planesResult);
161 }
162 }
163 }
164
165 {
166 const VkFormatProperties formatProperties = getPhysicalDeviceFormatProperties(
167 context.getInstanceInterface(), context.getPhysicalDevice(), params.format);
168
169 const bool transferByViews = disjoint && (getPlanarFormatDescription(params.format).numPlanes > 1);
170
171 if (!disjoint && (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) == 0)
172 TCU_THROW(NotSupportedError, "Storage images are not supported for this format");
173
174 if (disjoint && ((formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DISJOINT_BIT) == 0))
175 TCU_THROW(NotSupportedError, "Disjoint planes are not supported for this format");
176
177 if (disjoint && transferByViews)
178 {
179 const PlanarFormatDescription formatDescription = getPlanarFormatDescription(params.format);
180 for (uint32_t planeNdx = 0; planeNdx < formatDescription.numPlanes; ++planeNdx)
181 {
182 const VkFormat planeCompatibleFormat = getPlaneCompatibleFormatForWriting(formatDescription, planeNdx);
183 const VkFormatProperties planeCompatibleFormatProperties = getPhysicalDeviceFormatProperties(
184 context.getInstanceInterface(), context.getPhysicalDevice(), planeCompatibleFormat);
185
186 if ((planeCompatibleFormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) == 0)
187 TCU_THROW(NotSupportedError, "Storage images are not supported for the plane compatible format");
188 }
189 }
190 }
191 }
192
193 template <typename T>
makeVkSharedPtr(vk::Move<T> vkMove)194 inline de::SharedPtr<vk::Unique<T>> makeVkSharedPtr(vk::Move<T> vkMove)
195 {
196 return de::SharedPtr<vk::Unique<T>>(new vk::Unique<T>(vkMove));
197 }
198
computeWorkGroupSize(const VkExtent3D & planeExtent)199 tcu::UVec3 computeWorkGroupSize(const VkExtent3D &planeExtent)
200 {
201 const uint32_t maxComputeWorkGroupInvocations = 128u;
202 const tcu::UVec3 maxComputeWorkGroupSize = tcu::UVec3(128u, 128u, 64u);
203
204 const uint32_t xWorkGroupSize =
205 std::min(std::min(planeExtent.width, maxComputeWorkGroupSize.x()), maxComputeWorkGroupInvocations);
206 const uint32_t yWorkGroupSize = std::min(std::min(planeExtent.height, maxComputeWorkGroupSize.y()),
207 maxComputeWorkGroupInvocations / xWorkGroupSize);
208 const uint32_t zWorkGroupSize = std::min(std::min(planeExtent.depth, maxComputeWorkGroupSize.z()),
209 maxComputeWorkGroupInvocations / (xWorkGroupSize * yWorkGroupSize));
210
211 return tcu::UVec3(xWorkGroupSize, yWorkGroupSize, zWorkGroupSize);
212 }
213
testStorageImageWrite(Context & context,TestParameters params)214 tcu::TestStatus testStorageImageWrite(Context &context, TestParameters params)
215 {
216 const DeviceInterface &vkd = context.getDeviceInterface();
217 const VkDevice device = context.getDevice();
218 const uint32_t queueFamilyIndex = context.getUniversalQueueFamilyIndex();
219 const VkQueue queue = context.getUniversalQueue();
220 const PlanarFormatDescription formatDescription = getPlanarFormatDescription(params.format);
221 const bool disjoint = (params.flags & VK_IMAGE_CREATE_DISJOINT_BIT) != 0;
222 const bool transferByViews = disjoint && (formatDescription.numPlanes > 1);
223
224 VkImageCreateInfo imageCreateInfo = {
225 VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
226 DE_NULL,
227 params.flags,
228 VK_IMAGE_TYPE_2D,
229 params.format,
230 makeExtent3D(params.size.x(), params.size.y(), params.size.z()),
231 1u, // mipLevels
232 1u, // arrayLayers
233 VK_SAMPLE_COUNT_1_BIT,
234 VK_IMAGE_TILING_OPTIMAL,
235 VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_STORAGE_BIT,
236 VK_SHARING_MODE_EXCLUSIVE,
237 0u,
238 (const uint32_t *)DE_NULL,
239 VK_IMAGE_LAYOUT_UNDEFINED,
240 };
241
242 // check if we need to create VkImageView with different VkFormat than VkImage format
243 VkFormat planeCompatibleFormat0 = getPlaneCompatibleFormatForWriting(formatDescription, 0);
244 if (planeCompatibleFormat0 != params.format)
245 {
246 imageCreateInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
247 }
248
249 const Unique<VkImage> image(createImage(vkd, device, &imageCreateInfo));
250 // allocate memory for the whole image, or for each separate plane ( if the params.flags include VK_IMAGE_CREATE_DISJOINT_BIT )
251 const std::vector<AllocationSp> allocations(allocateAndBindImageMemory(
252 vkd, device, context.getDefaultAllocator(), *image, params.format, params.flags, MemoryRequirement::Any));
253
254 // Create descriptor set layout
255 const Unique<VkDescriptorSetLayout> descriptorSetLayout(
256 DescriptorSetLayoutBuilder()
257 .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT)
258 .build(vkd, device));
259 const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vkd, device, *descriptorSetLayout));
260
261 // Create descriptor sets
262 const Unique<VkDescriptorPool> descriptorPool(
263 DescriptorPoolBuilder()
264 .addType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, vk::PlanarFormatDescription::MAX_PLANES)
265 .build(vkd, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
266 vk::PlanarFormatDescription::MAX_PLANES));
267
268 // Create command buffer for compute and transfer operations
269 const Unique<VkCommandPool> commandPool(makeCommandPool(vkd, device, queueFamilyIndex));
270 const Unique<VkCommandBuffer> commandBuffer(
271 allocateCommandBuffer(vkd, device, *commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
272
273 std::vector<de::SharedPtr<vk::Unique<vk::VkShaderModule>>> shaderModules;
274 std::vector<de::SharedPtr<vk::Unique<vk::VkPipeline>>> computePipelines;
275 std::vector<de::SharedPtr<vk::Unique<vk::VkDescriptorSet>>> descriptorSets;
276 std::vector<de::SharedPtr<vk::Unique<vk::VkImageView>>> imageViews;
277
278 uint32_t imageSizeInBytes = 0;
279 uint32_t planeOffsets[PlanarFormatDescription::MAX_PLANES];
280 uint32_t planeRowPitches[PlanarFormatDescription::MAX_PLANES];
281 void *planePointers[PlanarFormatDescription::MAX_PLANES];
282
283 {
284 // Start recording commands
285 beginCommandBuffer(vkd, *commandBuffer);
286
287 for (uint32_t planeNdx = 0; planeNdx < formatDescription.numPlanes; ++planeNdx)
288 {
289 const VkImageAspectFlags aspect =
290 (formatDescription.numPlanes > 1) ? getPlaneAspect(planeNdx) : VK_IMAGE_ASPECT_COLOR_BIT;
291 const VkImageSubresourceRange subresourceRange = makeImageSubresourceRange(aspect, 0u, 1u, 0u, 1u);
292 VkFormat planeCompatibleFormat = getPlaneCompatibleFormatForWriting(formatDescription, planeNdx);
293 vk::PlanarFormatDescription compatibleFormatDescription =
294 (planeCompatibleFormat != getPlaneCompatibleFormat(formatDescription, planeNdx)) ?
295 getPlanarFormatDescription(planeCompatibleFormat) :
296 formatDescription;
297 const tcu::UVec3 compatibleShaderGridSize(params.size.x() / formatDescription.blockWidth,
298 params.size.y() / formatDescription.blockHeight,
299 params.size.z() / 1u);
300 VkExtent3D shaderExtent = getPlaneExtent(
301 compatibleFormatDescription,
302 VkExtent3D{compatibleShaderGridSize.x(), compatibleShaderGridSize.y(), compatibleShaderGridSize.z()},
303 planeNdx, 0u);
304
305 // Create and bind compute pipeline
306 std::ostringstream shaderName;
307 shaderName << "comp" << planeNdx;
308 auto shaderModule = makeVkSharedPtr(
309 createShaderModule(vkd, device, context.getBinaryCollection().get(shaderName.str()), DE_NULL));
310 shaderModules.push_back(shaderModule);
311 auto computePipeline = makeVkSharedPtr(
312 makeComputePipeline(vkd, device, *pipelineLayout, (VkPipelineCreateFlags)0u, nullptr,
313 shaderModule->get(), (VkPipelineShaderStageCreateFlags)0u, DE_NULL));
314 computePipelines.push_back(computePipeline);
315 vkd.cmdBindPipeline(*commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline->get());
316
317 auto descriptorSet = makeVkSharedPtr(makeDescriptorSet(vkd, device, *descriptorPool, *descriptorSetLayout));
318 descriptorSets.push_back(descriptorSet);
319
320 VkImageViewUsageCreateInfo imageViewUsageCreateInfo = {
321 VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, //VkStructureType sType;
322 DE_NULL, //const void* pNext;
323 VK_IMAGE_USAGE_STORAGE_BIT, //VkImageUsageFlags usage;
324 };
325
326 auto imageView =
327 makeVkSharedPtr(makeImageView(vkd, device, *image, VK_IMAGE_VIEW_TYPE_2D, planeCompatibleFormat,
328 subresourceRange, transferByViews ? &imageViewUsageCreateInfo : DE_NULL));
329 imageViews.push_back(imageView);
330 const VkDescriptorImageInfo imageInfo =
331 makeDescriptorImageInfo(DE_NULL, imageView->get(), VK_IMAGE_LAYOUT_GENERAL);
332
333 DescriptorSetUpdateBuilder()
334 .writeSingle(descriptorSet->get(), DescriptorSetUpdateBuilder::Location::binding(0u),
335 VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, &imageInfo)
336 .update(vkd, device);
337
338 vkd.cmdBindDescriptorSets(*commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, *pipelineLayout, 0u, 1u,
339 &descriptorSet->get(), 0u, DE_NULL);
340
341 {
342 const VkImageMemoryBarrier imageLayoutChangeBarrier = makeImageMemoryBarrier(
343 0u, VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, *image,
344 subresourceRange, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED);
345 vkd.cmdPipelineBarrier(*commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
346 VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0u, 0u, DE_NULL, 0u, DE_NULL, 1u,
347 &imageLayoutChangeBarrier);
348 }
349
350 {
351 const tcu::UVec3 workGroupSize = computeWorkGroupSize(shaderExtent);
352
353 const uint32_t xWorkGroupCount =
354 shaderExtent.width / workGroupSize.x() + (shaderExtent.width % workGroupSize.x() ? 1u : 0u);
355 const uint32_t yWorkGroupCount =
356 shaderExtent.height / workGroupSize.y() + (shaderExtent.height % workGroupSize.y() ? 1u : 0u);
357 const uint32_t zWorkGroupCount =
358 shaderExtent.depth / workGroupSize.z() + (shaderExtent.depth % workGroupSize.z() ? 1u : 0u);
359
360 const tcu::UVec3 maxComputeWorkGroupCount = tcu::UVec3(65535u, 65535u, 65535u);
361
362 if (maxComputeWorkGroupCount.x() < xWorkGroupCount || maxComputeWorkGroupCount.y() < yWorkGroupCount ||
363 maxComputeWorkGroupCount.z() < zWorkGroupCount)
364 {
365 TCU_THROW(NotSupportedError, "Image size is not supported");
366 }
367
368 vkd.cmdDispatch(*commandBuffer, xWorkGroupCount, yWorkGroupCount, zWorkGroupCount);
369 }
370
371 {
372 const VkImageMemoryBarrier imageTransferBarrier = makeImageMemoryBarrier(
373 VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL,
374 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, *image, subresourceRange);
375 vkd.cmdPipelineBarrier(*commandBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
376 VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, DE_NULL, 0u, DE_NULL, 1u,
377 &imageTransferBarrier);
378 }
379 }
380
381 for (uint32_t planeNdx = 0; planeNdx < formatDescription.numPlanes; ++planeNdx)
382 {
383 planeOffsets[planeNdx] = imageSizeInBytes;
384 const uint32_t planeW = imageCreateInfo.extent.width /
385 (formatDescription.blockWidth * formatDescription.planes[planeNdx].widthDivisor);
386 planeRowPitches[planeNdx] = formatDescription.planes[planeNdx].elementSizeBytes * planeW;
387 imageSizeInBytes +=
388 getPlaneSizeInBytes(formatDescription, makeExtent3D(params.size.x(), params.size.y(), params.size.z()),
389 planeNdx, 0u, BUFFER_IMAGE_COPY_OFFSET_GRANULARITY);
390 }
391
392 const VkBufferCreateInfo outputBufferCreateInfo =
393 makeBufferCreateInfo(imageSizeInBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
394 const Unique<VkBuffer> outputBuffer(createBuffer(vkd, device, &outputBufferCreateInfo));
395 const de::UniquePtr<Allocation> outputBufferAlloc(
396 bindBuffer(vkd, device, context.getDefaultAllocator(), *outputBuffer, MemoryRequirement::HostVisible));
397 std::vector<VkBufferImageCopy> bufferImageCopy(formatDescription.numPlanes);
398
399 for (uint32_t planeNdx = 0; planeNdx < formatDescription.numPlanes; ++planeNdx)
400 {
401 const VkImageAspectFlags aspect =
402 (formatDescription.numPlanes > 1) ? getPlaneAspect(planeNdx) : VK_IMAGE_ASPECT_COLOR_BIT;
403
404 bufferImageCopy[planeNdx] = {
405 planeOffsets[planeNdx], // VkDeviceSize bufferOffset;
406 0u, // uint32_t bufferRowLength;
407 0u, // uint32_t bufferImageHeight;
408 makeImageSubresourceLayers(aspect, 0u, 0u, 1u), // VkImageSubresourceLayers imageSubresource;
409 makeOffset3D(0, 0, 0), // VkOffset3D imageOffset;
410 getPlaneExtent(formatDescription, makeExtent3D(params.size.x(), params.size.y(), params.size.z()),
411 planeNdx, 0u) // VkExtent3D imageExtent;
412 };
413 }
414 vkd.cmdCopyImageToBuffer(*commandBuffer, *image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, *outputBuffer,
415 static_cast<uint32_t>(bufferImageCopy.size()), bufferImageCopy.data());
416
417 {
418 const VkBufferMemoryBarrier outputBufferHostReadBarrier = makeBufferMemoryBarrier(
419 VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *outputBuffer, 0u, imageSizeInBytes);
420
421 vkd.cmdPipelineBarrier(*commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u,
422 DE_NULL, 1u, &outputBufferHostReadBarrier, 0u, DE_NULL);
423 }
424
425 // End recording commands
426 endCommandBuffer(vkd, *commandBuffer);
427
428 // Submit commands for execution and wait for completion
429 submitCommandsAndWait(vkd, device, queue, *commandBuffer);
430
431 // Retrieve data from buffer to host memory
432 invalidateAlloc(vkd, device, *outputBufferAlloc);
433 uint8_t *outputData = static_cast<uint8_t *>(outputBufferAlloc->getHostPtr());
434
435 for (uint32_t planeNdx = 0; planeNdx < formatDescription.numPlanes; ++planeNdx)
436 planePointers[planeNdx] = outputData + static_cast<size_t>(planeOffsets[planeNdx]);
437
438 // write result images to log file
439 for (uint32_t channelNdx = 0; channelNdx < 4; ++channelNdx)
440 {
441 if (!formatDescription.hasChannelNdx(channelNdx))
442 continue;
443 uint32_t planeNdx = formatDescription.channels[channelNdx].planeNdx;
444 vk::VkFormat planeCompatibleFormat = getPlaneCompatibleFormatForWriting(formatDescription, planeNdx);
445 vk::PlanarFormatDescription compatibleFormatDescription =
446 (planeCompatibleFormat != getPlaneCompatibleFormat(formatDescription, planeNdx)) ?
447 getPlanarFormatDescription(planeCompatibleFormat) :
448 formatDescription;
449 const tcu::UVec3 compatibleShaderGridSize(params.size.x() / formatDescription.blockWidth,
450 params.size.y() / formatDescription.blockHeight,
451 params.size.z() / 1u);
452 tcu::ConstPixelBufferAccess pixelBuffer =
453 vk::getChannelAccess(compatibleFormatDescription, compatibleShaderGridSize, planeRowPitches,
454 (const void *const *)planePointers, channelNdx);
455 std::ostringstream str;
456 str << "image" << channelNdx;
457 context.getTestContext().getLog() << tcu::LogImage(str.str(), str.str(), pixelBuffer);
458 }
459
460 // verify data
461 const float epsilon = 1e-5f;
462 for (uint32_t channelNdx = 0; channelNdx < 4; ++channelNdx)
463 {
464 if (!formatDescription.hasChannelNdx(channelNdx))
465 continue;
466
467 uint32_t planeNdx = formatDescription.channels[channelNdx].planeNdx;
468 vk::VkFormat planeCompatibleFormat = getPlaneCompatibleFormatForWriting(formatDescription, planeNdx);
469 vk::PlanarFormatDescription compatibleFormatDescription =
470 (planeCompatibleFormat != getPlaneCompatibleFormat(formatDescription, planeNdx)) ?
471 getPlanarFormatDescription(planeCompatibleFormat) :
472 formatDescription;
473 const tcu::UVec3 compatibleShaderGridSize(params.size.x() / formatDescription.blockWidth,
474 params.size.y() / formatDescription.blockHeight,
475 params.size.z() / 1u);
476 VkExtent3D compatibleImageSize{imageCreateInfo.extent.width / formatDescription.blockWidth,
477 imageCreateInfo.extent.height / formatDescription.blockHeight,
478 imageCreateInfo.extent.depth / 1u};
479 tcu::ConstPixelBufferAccess pixelBuffer =
480 vk::getChannelAccess(compatibleFormatDescription, compatibleShaderGridSize, planeRowPitches,
481 (const void *const *)planePointers, channelNdx);
482 VkExtent3D planeExtent = getPlaneExtent(compatibleFormatDescription, compatibleImageSize, planeNdx, 0u);
483 tcu::IVec3 pixelDivider = pixelBuffer.getDivider();
484
485 for (uint32_t offsetZ = 0u; offsetZ < planeExtent.depth; ++offsetZ)
486 for (uint32_t offsetY = 0u; offsetY < planeExtent.height; ++offsetY)
487 for (uint32_t offsetX = 0u; offsetX < planeExtent.width; ++offsetX)
488 {
489 uint32_t iReferenceValue;
490 float fReferenceValue;
491 switch (channelNdx)
492 {
493 case 0:
494 iReferenceValue = offsetX % 127u;
495 fReferenceValue = static_cast<float>(iReferenceValue) / 127.f;
496 break;
497 case 1:
498 iReferenceValue = offsetY % 127u;
499 fReferenceValue = static_cast<float>(iReferenceValue) / 127.f;
500 break;
501 case 2:
502 iReferenceValue = offsetZ % 127u;
503 fReferenceValue = static_cast<float>(iReferenceValue) / 127.f;
504 break;
505 case 3:
506 iReferenceValue = 1u;
507 fReferenceValue = 1.f;
508 break;
509 default:
510 DE_FATAL("Unexpected channel index");
511 break;
512 }
513 float acceptableError = epsilon;
514
515 switch (formatDescription.channels[channelNdx].type)
516 {
517 case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
518 case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
519 {
520 tcu::UVec4 outputValue =
521 pixelBuffer.getPixelUint(offsetX * pixelDivider.x(), offsetY * pixelDivider.y(), 0);
522
523 if (outputValue.x() != iReferenceValue)
524 return tcu::TestStatus::fail("Failed");
525
526 break;
527 }
528 case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
529 case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
530 {
531 float fixedPointError = tcu::TexVerifierUtil::computeFixedPointError(
532 formatDescription.channels[channelNdx].sizeBits);
533 acceptableError += fixedPointError;
534 tcu::Vec4 outputValue =
535 pixelBuffer.getPixel(offsetX * pixelDivider.x(), offsetY * pixelDivider.y(), 0);
536
537 if (deAbs(outputValue.x() - fReferenceValue) > acceptableError)
538 return tcu::TestStatus::fail("Failed");
539
540 break;
541 }
542 case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
543 {
544 const tcu::Vec4 outputValue =
545 pixelBuffer.getPixel(offsetX * pixelDivider.x(), offsetY * pixelDivider.y(), 0);
546
547 if (deAbs(outputValue.x() - fReferenceValue) > acceptableError)
548 return tcu::TestStatus::fail("Failed");
549
550 break;
551 }
552 default:
553 DE_FATAL("Unexpected channel type");
554 break;
555 }
556 }
557 }
558 }
559 return tcu::TestStatus::pass("Passed");
560 }
561
getShaderImageType(const vk::PlanarFormatDescription & description)562 std::string getShaderImageType(const vk::PlanarFormatDescription &description)
563 {
564 std::string formatPart;
565
566 // all PlanarFormatDescription types have at least one channel ( 0 ) and all channel types are the same :
567 switch (description.channels[0].type)
568 {
569 case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
570 formatPart = "i";
571 break;
572 case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
573 formatPart = "u";
574 break;
575 case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
576 case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
577 case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
578 break;
579
580 default:
581 DE_FATAL("Unexpected channel type");
582 }
583
584 return formatPart + "image2D";
585 }
586
getShaderImageDataType(const vk::PlanarFormatDescription & description)587 std::string getShaderImageDataType(const vk::PlanarFormatDescription &description)
588 {
589 switch (description.channels[0].type)
590 {
591 case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
592 return "uvec4";
593 case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
594 return "ivec4";
595 case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
596 case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
597 case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
598 return "vec4";
599 default:
600 DE_FATAL("Unexpected channel type");
601 return "";
602 }
603 }
604
getFormatValueString(const std::vector<std::pair<uint32_t,uint32_t>> & channelsOnPlane,const std::vector<std::string> & formatValueStrings)605 std::string getFormatValueString(const std::vector<std::pair<uint32_t, uint32_t>> &channelsOnPlane,
606 const std::vector<std::string> &formatValueStrings)
607 {
608 std::string result = "( ";
609 uint32_t i;
610 for (i = 0; i < channelsOnPlane.size(); ++i)
611 {
612 result += formatValueStrings[channelsOnPlane[i].first];
613 if (i < 3)
614 result += ", ";
615 }
616 for (; i < 4; ++i)
617 {
618 result += "0";
619 if (i < 3)
620 result += ", ";
621 }
622 result += " )";
623 return result;
624 }
625
getShaderImageFormatQualifier(VkFormat format)626 std::string getShaderImageFormatQualifier(VkFormat format)
627 {
628 switch (format)
629 {
630 case VK_FORMAT_R8_SINT:
631 return "r8i";
632 case VK_FORMAT_R16_SINT:
633 return "r16i";
634 case VK_FORMAT_R32_SINT:
635 return "r32i";
636 case VK_FORMAT_R8_UINT:
637 return "r8ui";
638 case VK_FORMAT_R16_UINT:
639 return "r16ui";
640 case VK_FORMAT_R32_UINT:
641 return "r32ui";
642 case VK_FORMAT_R8_SNORM:
643 return "r8_snorm";
644 case VK_FORMAT_R16_SNORM:
645 return "r16_snorm";
646 case VK_FORMAT_R8_UNORM:
647 return "r8";
648 case VK_FORMAT_R16_UNORM:
649 return "r16";
650
651 case VK_FORMAT_R8G8_SINT:
652 return "rg8i";
653 case VK_FORMAT_R16G16_SINT:
654 return "rg16i";
655 case VK_FORMAT_R32G32_SINT:
656 return "rg32i";
657 case VK_FORMAT_R8G8_UINT:
658 return "rg8ui";
659 case VK_FORMAT_R16G16_UINT:
660 return "rg16ui";
661 case VK_FORMAT_R32G32_UINT:
662 return "rg32ui";
663 case VK_FORMAT_R8G8_SNORM:
664 return "rg8_snorm";
665 case VK_FORMAT_R16G16_SNORM:
666 return "rg16_snorm";
667 case VK_FORMAT_R8G8_UNORM:
668 return "rg8";
669 case VK_FORMAT_R16G16_UNORM:
670 return "rg16";
671
672 case VK_FORMAT_R8G8B8A8_SINT:
673 return "rgba8i";
674 case VK_FORMAT_R16G16B16A16_SINT:
675 return "rgba16i";
676 case VK_FORMAT_R32G32B32A32_SINT:
677 return "rgba32i";
678 case VK_FORMAT_R8G8B8A8_UINT:
679 return "rgba8ui";
680 case VK_FORMAT_R16G16B16A16_UINT:
681 return "rgba16ui";
682 case VK_FORMAT_R32G32B32A32_UINT:
683 return "rgba32ui";
684 case VK_FORMAT_R8G8B8A8_SNORM:
685 return "rgba8_snorm";
686 case VK_FORMAT_R16G16B16A16_SNORM:
687 return "rgba16_snorm";
688 case VK_FORMAT_R8G8B8A8_UNORM:
689 return "rgba8";
690 case VK_FORMAT_R16G16B16A16_UNORM:
691 return "rgba16";
692
693 case VK_FORMAT_G8B8G8R8_422_UNORM:
694 return "rgba8";
695 case VK_FORMAT_B8G8R8G8_422_UNORM:
696 return "rgba8";
697 case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
698 return "rgba8";
699 case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
700 return "rgba8";
701 case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
702 return "rgba8";
703 case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
704 return "rgba8";
705 case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
706 return "rgba8";
707 case VK_FORMAT_R10X6_UNORM_PACK16:
708 return "r16";
709 case VK_FORMAT_R10X6G10X6_UNORM_2PACK16:
710 return "rg16";
711 case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
712 return "rgba16";
713 case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:
714 return "rgba16";
715 case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:
716 return "rgba16";
717 case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
718 return "rgba16";
719 case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
720 return "rgba16";
721 case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
722 return "rgba16";
723 case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
724 return "rgba16";
725 case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
726 return "rgba16";
727 case VK_FORMAT_R12X4_UNORM_PACK16:
728 return "r16";
729 case VK_FORMAT_R12X4G12X4_UNORM_2PACK16:
730 return "rg16";
731 case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16:
732 return "rgba16";
733 case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:
734 return "rgba16";
735 case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:
736 return "rgba16";
737 case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
738 return "rgba16";
739 case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
740 return "rgba16";
741 case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
742 return "rgba16";
743 case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
744 return "rgba16";
745 case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
746 return "rgba16";
747 case VK_FORMAT_G16B16G16R16_422_UNORM:
748 return "rgba16";
749 case VK_FORMAT_B16G16R16G16_422_UNORM:
750 return "rgba16";
751 case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
752 return "rgba16";
753 case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
754 return "rgba16";
755 case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
756 return "rgba16";
757 case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
758 return "rgba16";
759 case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
760 return "rgba16";
761 case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT:
762 return "rgba8";
763 case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT:
764 return "rgba16";
765 case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT:
766 return "rgba16";
767 case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT:
768 return "rgba16";
769
770 default:
771 DE_FATAL("Unexpected texture format");
772 return "error";
773 }
774 }
775
initPrograms(SourceCollections & sourceCollections,TestParameters params)776 void initPrograms(SourceCollections &sourceCollections, TestParameters params)
777 {
778 // Create compute program
779 const char *const versionDecl = glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_440);
780 const PlanarFormatDescription formatDescription = getPlanarFormatDescription(params.format);
781 const std::string imageTypeStr = getShaderImageType(formatDescription);
782 const std::string formatDataStr = getShaderImageDataType(formatDescription);
783 const tcu::UVec3 shaderGridSize(params.size.x(), params.size.y(), params.size.z());
784
785 std::vector<std::string> formatValueStrings;
786 switch (formatDescription.channels[0].type)
787 {
788 case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
789 case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
790 formatValueStrings = {"int(gl_GlobalInvocationID.x) % 127", "int(gl_GlobalInvocationID.y) % 127",
791 "int(gl_GlobalInvocationID.z) % 127", "1"};
792 break;
793 case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
794 case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
795 case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
796 formatValueStrings = {"float(int(gl_GlobalInvocationID.x) % 127) / 127.0",
797 "float(int(gl_GlobalInvocationID.y) % 127) / 127.0",
798 "float(int(gl_GlobalInvocationID.z) % 127) / 127.0", "1.0"};
799 break;
800 default:
801 DE_ASSERT(false);
802 break;
803 }
804
805 for (uint32_t planeNdx = 0; planeNdx < formatDescription.numPlanes; ++planeNdx)
806 {
807 VkFormat planeCompatibleFormat = getPlaneCompatibleFormatForWriting(formatDescription, planeNdx);
808 vk::PlanarFormatDescription compatibleFormatDescription =
809 (planeCompatibleFormat != getPlaneCompatibleFormat(formatDescription, planeNdx)) ?
810 getPlanarFormatDescription(planeCompatibleFormat) :
811 formatDescription;
812 VkExtent3D compatibleShaderGridSize{shaderGridSize.x() / formatDescription.blockWidth,
813 shaderGridSize.y() / formatDescription.blockHeight,
814 shaderGridSize.z() / 1u};
815
816 std::vector<std::pair<uint32_t, uint32_t>> channelsOnPlane;
817 for (uint32_t channelNdx = 0; channelNdx < 4; ++channelNdx)
818 {
819 if (!formatDescription.hasChannelNdx(channelNdx))
820 continue;
821 if (formatDescription.channels[channelNdx].planeNdx != planeNdx)
822 continue;
823 channelsOnPlane.push_back({channelNdx, formatDescription.channels[channelNdx].offsetBits});
824 }
825 // reorder channels for multi-planar images
826 if (formatDescription.numPlanes > 1)
827 std::sort(begin(channelsOnPlane), end(channelsOnPlane),
828 [](const std::pair<uint32_t, uint32_t> &lhs, const std::pair<uint32_t, uint32_t> &rhs)
829 { return lhs.second < rhs.second; });
830 std::string formatValueStr = getFormatValueString(channelsOnPlane, formatValueStrings);
831 VkExtent3D shaderExtent = getPlaneExtent(compatibleFormatDescription, compatibleShaderGridSize, planeNdx, 0);
832 const std::string formatQualifierStr =
833 getShaderImageFormatQualifier(formatDescription.planes[planeNdx].planeCompatibleFormat);
834 const tcu::UVec3 workGroupSize = computeWorkGroupSize(shaderExtent);
835
836 std::ostringstream src;
837 src << versionDecl << "\n"
838 << "layout (local_size_x = " << workGroupSize.x() << ", local_size_y = " << workGroupSize.y()
839 << ", local_size_z = " << workGroupSize.z() << ") in; \n"
840 << "layout (binding = 0, " << formatQualifierStr << ") writeonly uniform highp " << imageTypeStr
841 << " u_image;\n"
842 << "void main (void)\n"
843 << "{\n"
844 << " if( gl_GlobalInvocationID.x < " << shaderExtent.width << " ) \n"
845 << " if( gl_GlobalInvocationID.y < " << shaderExtent.height << " ) \n"
846 << " if( gl_GlobalInvocationID.z < " << shaderExtent.depth << " ) \n"
847 << " {\n"
848 << " imageStore(u_image, ivec2( gl_GlobalInvocationID.x, gl_GlobalInvocationID.y ) ,"
849 << formatDataStr << formatValueStr << ");\n"
850 << " }\n"
851 << "}\n";
852 std::ostringstream shaderName;
853 shaderName << "comp" << planeNdx;
854 sourceCollections.glslSources.add(shaderName.str()) << glu::ComputeSource(src.str());
855 }
856 }
857
populateStorageImageWriteFormatGroup(tcu::TestContext & testCtx,de::MovePtr<tcu::TestCaseGroup> testGroup)858 tcu::TestCaseGroup *populateStorageImageWriteFormatGroup(tcu::TestContext &testCtx,
859 de::MovePtr<tcu::TestCaseGroup> testGroup)
860 {
861 const std::vector<tcu::UVec3> availableSizes{tcu::UVec3(512u, 512u, 1u), tcu::UVec3(1024u, 128u, 1u),
862 tcu::UVec3(66u, 32u, 1u)};
863
864 auto addTests = [&](int formatNdx)
865 {
866 const VkFormat format = (VkFormat)formatNdx;
867 tcu::UVec3 imageSizeAlignment = getImageSizeAlignment(format);
868 std::string formatName = de::toLower(de::toString(format).substr(10));
869 de::MovePtr<tcu::TestCaseGroup> formatGroup(new tcu::TestCaseGroup(testCtx, formatName.c_str()));
870
871 for (size_t sizeNdx = 0; sizeNdx < availableSizes.size(); sizeNdx++)
872 {
873 const tcu::UVec3 imageSize = availableSizes[sizeNdx];
874
875 // skip test for images with odd sizes for some YCbCr formats
876 if ((imageSize.x() % imageSizeAlignment.x()) != 0)
877 continue;
878 if ((imageSize.y() % imageSizeAlignment.y()) != 0)
879 continue;
880
881 std::ostringstream stream;
882 stream << imageSize.x() << "_" << imageSize.y() << "_" << imageSize.z();
883 de::MovePtr<tcu::TestCaseGroup> sizeGroup(new tcu::TestCaseGroup(testCtx, stream.str().c_str()));
884
885 addFunctionCaseWithPrograms(sizeGroup.get(), "joint", checkSupport, initPrograms, testStorageImageWrite,
886 TestParameters(format, imageSize, 0u));
887 addFunctionCaseWithPrograms(sizeGroup.get(), "disjoint", checkSupport, initPrograms, testStorageImageWrite,
888 TestParameters(format, imageSize,
889 (VkImageCreateFlags)(VK_IMAGE_CREATE_DISJOINT_BIT |
890 VK_IMAGE_CREATE_EXTENDED_USAGE_BIT)));
891
892 formatGroup->addChild(sizeGroup.release());
893 }
894 testGroup->addChild(formatGroup.release());
895 };
896
897 for (int formatNdx = VK_YCBCR_FORMAT_FIRST; formatNdx < VK_YCBCR_FORMAT_LAST; formatNdx++)
898 {
899 addTests(formatNdx);
900 }
901
902 for (int formatNdx = VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT; formatNdx <= VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT;
903 formatNdx++)
904 {
905 addTests(formatNdx);
906 }
907
908 return testGroup.release();
909 }
910
911 } // namespace
912
createStorageImageWriteTests(tcu::TestContext & testCtx)913 tcu::TestCaseGroup *createStorageImageWriteTests(tcu::TestContext &testCtx)
914 {
915 de::MovePtr<tcu::TestCaseGroup> testGroup(new tcu::TestCaseGroup(testCtx, "storage_image_write"));
916 return populateStorageImageWriteFormatGroup(testCtx, testGroup);
917 }
918
919 } // namespace ycbcr
920 } // namespace vkt
921