1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "GrVkCaps.h"
9 #include "GrBackendSurface.h"
10 #include "GrRenderTargetProxy.h"
11 #include "GrRenderTarget.h"
12 #include "GrShaderCaps.h"
13 #include "GrVkUtil.h"
14 #include "vk/GrVkBackendContext.h"
15 #include "vk/GrVkInterface.h"
16 
17 GrVkCaps::GrVkCaps(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface,
18                    VkPhysicalDevice physDev, uint32_t featureFlags, uint32_t extensionFlags)
19     : INHERITED(contextOptions) {
20     fCanUseGLSLForShaderModule = false;
21     fMustDoCopiesFromOrigin = false;
22     fSupportsCopiesAsDraws = true;
23     fMustSubmitCommandsBeforeCopyOp = false;
24     fMustSleepOnTearDown  = false;
25     fNewCBOnPipelineChange = false;
26     fCanUseWholeSizeOnFlushMappedMemory = true;
27 
28     /**************************************************************************
29     * GrDrawTargetCaps fields
30     **************************************************************************/
31     fMipMapSupport = true;   // always available in Vulkan
32     fSRGBSupport = true;   // always available in Vulkan
33     fSRGBDecodeDisableSupport = true;  // always available in Vulkan
34     fNPOTTextureTileSupport = true;  // always available in Vulkan
35     fDiscardRenderTargetSupport = true;
36     fReuseScratchTextures = true; //TODO: figure this out
37     fGpuTracingSupport = false; //TODO: figure this out
38     fOversizedStencilSupport = false; //TODO: figure this out
39     fInstanceAttribSupport = true;
40 
41     fBlacklistCoverageCounting = true; // blacklisting ccpr until we work through a few issues.
42     fFenceSyncSupport = true;   // always available in Vulkan
43     fCrossContextTextureSupport = true;
44 
45     fMapBufferFlags = kNone_MapFlags; //TODO: figure this out
46     fBufferMapThreshold = SK_MaxS32;  //TODO: figure this out
47 
48     fMaxRenderTargetSize = 4096; // minimum required by spec
49     fMaxTextureSize = 4096; // minimum required by spec
50 
51     fShaderCaps.reset(new GrShaderCaps(contextOptions));
52 
53     this->init(contextOptions, vkInterface, physDev, featureFlags, extensionFlags);
54 }
55 
56 bool GrVkCaps::initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc,
57                                   bool* rectsMustMatch, bool* disallowSubrect) const {
58     // Vk doesn't use rectsMustMatch or disallowSubrect. Always return false.
59     *rectsMustMatch = false;
60     *disallowSubrect = false;
61 
62     // We can always succeed here with either a CopyImage (none msaa src) or ResolveImage (msaa).
63     // For CopyImage we can make a simple texture, for ResolveImage we require the dst to be a
64     // render target as well.
65     desc->fOrigin = src->origin();
66     desc->fConfig = src->config();
67     if (src->numColorSamples() > 1 || (src->asTextureProxy() && this->supportsCopiesAsDraws())) {
68         desc->fFlags = kRenderTarget_GrSurfaceFlag;
69     } else {
70         // Just going to use CopyImage here
71         desc->fFlags = kNone_GrSurfaceFlags;
72     }
73 
74     return true;
75 }
76 
77 void GrVkCaps::init(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface,
78                     VkPhysicalDevice physDev, uint32_t featureFlags, uint32_t extensionFlags) {
79 
80     VkPhysicalDeviceProperties properties;
81     GR_VK_CALL(vkInterface, GetPhysicalDeviceProperties(physDev, &properties));
82 
83     VkPhysicalDeviceMemoryProperties memoryProperties;
84     GR_VK_CALL(vkInterface, GetPhysicalDeviceMemoryProperties(physDev, &memoryProperties));
85 
86     this->initGrCaps(properties, memoryProperties, featureFlags);
87     this->initShaderCaps(properties, featureFlags);
88 
89     if (!contextOptions.fDisableDriverCorrectnessWorkarounds) {
90 #if defined(SK_CPU_X86)
91         // We need to do this before initing the config table since it uses fSRGBSupport
92         if (kImagination_VkVendor == properties.vendorID) {
93             fSRGBSupport = false;
94         }
95 #endif
96     }
97 
98     this->initConfigTable(vkInterface, physDev, properties);
99     this->initStencilFormat(vkInterface, physDev);
100 
101     if (!contextOptions.fDisableDriverCorrectnessWorkarounds) {
102         this->applyDriverCorrectnessWorkarounds(properties);
103     }
104 
105     this->applyOptionsOverrides(contextOptions);
106     fShaderCaps->applyOptionsOverrides(contextOptions);
107 }
108 
109 void GrVkCaps::applyDriverCorrectnessWorkarounds(const VkPhysicalDeviceProperties& properties) {
110     if (kQualcomm_VkVendor == properties.vendorID) {
111         fMustDoCopiesFromOrigin = true;
112     }
113 
114     if (kNvidia_VkVendor == properties.vendorID) {
115         fMustSubmitCommandsBeforeCopyOp = true;
116     }
117 
118     if (kQualcomm_VkVendor == properties.vendorID ||
119         kARM_VkVendor == properties.vendorID) {
120         fSupportsCopiesAsDraws = false;
121         // We require copies as draws to support cross context textures.
122         fCrossContextTextureSupport = false;
123     }
124 
125 #if defined(SK_BUILD_FOR_WIN)
126     if (kNvidia_VkVendor == properties.vendorID) {
127         fMustSleepOnTearDown = true;
128     }
129 #elif defined(SK_BUILD_FOR_ANDROID)
130     if (kImagination_VkVendor == properties.vendorID) {
131         fMustSleepOnTearDown = true;
132     }
133 #endif
134 
135     // AMD seems to have issues binding new VkPipelines inside a secondary command buffer.
136     // Current workaround is to use a different secondary command buffer for each new VkPipeline.
137     if (kAMD_VkVendor == properties.vendorID) {
138         fNewCBOnPipelineChange = true;
139     }
140 
141     ////////////////////////////////////////////////////////////////////////////
142     // GrCaps workarounds
143     ////////////////////////////////////////////////////////////////////////////
144 
145     if (kARM_VkVendor == properties.vendorID) {
146         fInstanceAttribSupport = false;
147     }
148 
149     // AMD advertises support for MAX_UINT vertex input attributes, but in reality only supports 32.
150     if (kAMD_VkVendor == properties.vendorID) {
151         fMaxVertexAttributes = SkTMin(fMaxVertexAttributes, 32);
152     }
153 
154     if (kIntel_VkVendor == properties.vendorID) {
155         fCanUseWholeSizeOnFlushMappedMemory = false;
156     }
157 
158     ////////////////////////////////////////////////////////////////////////////
159     // GrShaderCaps workarounds
160     ////////////////////////////////////////////////////////////////////////////
161 
162     if (kImagination_VkVendor == properties.vendorID) {
163         fShaderCaps->fAtan2ImplementedAsAtanYOverX = true;
164     }
165 
166 }
167 
168 int get_max_sample_count(VkSampleCountFlags flags) {
169     SkASSERT(flags & VK_SAMPLE_COUNT_1_BIT);
170     if (!(flags & VK_SAMPLE_COUNT_2_BIT)) {
171         return 0;
172     }
173     if (!(flags & VK_SAMPLE_COUNT_4_BIT)) {
174         return 2;
175     }
176     if (!(flags & VK_SAMPLE_COUNT_8_BIT)) {
177         return 4;
178     }
179     if (!(flags & VK_SAMPLE_COUNT_16_BIT)) {
180         return 8;
181     }
182     if (!(flags & VK_SAMPLE_COUNT_32_BIT)) {
183         return 16;
184     }
185     if (!(flags & VK_SAMPLE_COUNT_64_BIT)) {
186         return 32;
187     }
188     return 64;
189 }
190 
191 void GrVkCaps::initGrCaps(const VkPhysicalDeviceProperties& properties,
192                           const VkPhysicalDeviceMemoryProperties& memoryProperties,
193                           uint32_t featureFlags) {
194     // So GPUs, like AMD, are reporting MAX_INT support vertex attributes. In general, there is no
195     // need for us ever to support that amount, and it makes tests which tests all the vertex
196     // attribs timeout looping over that many. For now, we'll cap this at 64 max and can raise it if
197     // we ever find that need.
198     static const uint32_t kMaxVertexAttributes = 64;
199     fMaxVertexAttributes = SkTMin(properties.limits.maxVertexInputAttributes, kMaxVertexAttributes);
200 
201     // We could actually query and get a max size for each config, however maxImageDimension2D will
202     // give the minimum max size across all configs. So for simplicity we will use that for now.
203     fMaxRenderTargetSize = SkTMin(properties.limits.maxImageDimension2D, (uint32_t)INT_MAX);
204     fMaxTextureSize = SkTMin(properties.limits.maxImageDimension2D, (uint32_t)INT_MAX);
205 
206     // TODO: check if RT's larger than 4k incur a performance cost on ARM.
207     fMaxPreferredRenderTargetSize = fMaxRenderTargetSize;
208 
209     // Assuming since we will always map in the end to upload the data we might as well just map
210     // from the get go. There is no hard data to suggest this is faster or slower.
211     fBufferMapThreshold = 0;
212 
213     fMapBufferFlags = kCanMap_MapFlag | kSubset_MapFlag;
214 
215     fOversizedStencilSupport = true;
216     fSampleShadingSupport = SkToBool(featureFlags & kSampleRateShading_GrVkFeatureFlag);
217 
218 
219 }
220 
221 void GrVkCaps::initShaderCaps(const VkPhysicalDeviceProperties& properties, uint32_t featureFlags) {
222     GrShaderCaps* shaderCaps = fShaderCaps.get();
223     shaderCaps->fVersionDeclString = "#version 330\n";
224 
225 
226     // fConfigOutputSwizzle will default to RGBA so we only need to set it for alpha only config.
227     for (int i = 0; i < kGrPixelConfigCnt; ++i) {
228         GrPixelConfig config = static_cast<GrPixelConfig>(i);
229         // Vulkan doesn't support a single channel format stored in alpha.
230         if (GrPixelConfigIsAlphaOnly(config) &&
231             kAlpha_8_as_Alpha_GrPixelConfig != config) {
232             shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRR();
233             shaderCaps->fConfigOutputSwizzle[i] = GrSwizzle::AAAA();
234         } else {
235             if (kGray_8_GrPixelConfig == config ||
236                 kGray_8_as_Red_GrPixelConfig == config) {
237                 shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRA();
238             } else if (kRGBA_4444_GrPixelConfig == config) {
239                 // The vulkan spec does not require R4G4B4A4 to be supported for texturing so we
240                 // store the data in a B4G4R4A4 texture and then swizzle it when doing texture reads
241                 // or writing to outputs. Since we're not actually changing the data at all, the
242                 // only extra work is the swizzle in the shader for all operations.
243                 shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::BGRA();
244                 shaderCaps->fConfigOutputSwizzle[i] = GrSwizzle::BGRA();
245             } else {
246                 shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGBA();
247             }
248         }
249     }
250 
251     // Vulkan is based off ES 3.0 so the following should all be supported
252     shaderCaps->fUsesPrecisionModifiers = true;
253     shaderCaps->fFlatInterpolationSupport = true;
254     // Flat interpolation appears to be slow on Qualcomm GPUs. This was tested in GL and is assumed
255     // to be true with Vulkan as well.
256     shaderCaps->fPreferFlatInterpolation = kQualcomm_VkVendor != properties.vendorID;
257 
258     // GrShaderCaps
259 
260     shaderCaps->fShaderDerivativeSupport = true;
261 
262     shaderCaps->fGeometryShaderSupport = SkToBool(featureFlags & kGeometryShader_GrVkFeatureFlag);
263     shaderCaps->fGSInvocationsSupport = shaderCaps->fGeometryShaderSupport;
264 
265     shaderCaps->fDualSourceBlendingSupport = SkToBool(featureFlags & kDualSrcBlend_GrVkFeatureFlag);
266 
267     shaderCaps->fIntegerSupport = true;
268     shaderCaps->fTexelBufferSupport = true;
269     shaderCaps->fTexelFetchSupport = true;
270     shaderCaps->fVertexIDSupport = true;
271 
272     // Assume the minimum precisions mandated by the SPIR-V spec.
273     shaderCaps->fFloatIs32Bits = true;
274     shaderCaps->fHalfIs32Bits = false;
275 
276     shaderCaps->fMaxVertexSamplers =
277     shaderCaps->fMaxGeometrySamplers =
278     shaderCaps->fMaxFragmentSamplers = SkTMin(
279                                        SkTMin(properties.limits.maxPerStageDescriptorSampledImages,
280                                               properties.limits.maxPerStageDescriptorSamplers),
281                                               (uint32_t)INT_MAX);
282     shaderCaps->fMaxCombinedSamplers = SkTMin(
283                                        SkTMin(properties.limits.maxDescriptorSetSampledImages,
284                                               properties.limits.maxDescriptorSetSamplers),
285                                               (uint32_t)INT_MAX);
286 }
287 
288 bool stencil_format_supported(const GrVkInterface* interface,
289                               VkPhysicalDevice physDev,
290                               VkFormat format) {
291     VkFormatProperties props;
292     memset(&props, 0, sizeof(VkFormatProperties));
293     GR_VK_CALL(interface, GetPhysicalDeviceFormatProperties(physDev, format, &props));
294     return SkToBool(VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT & props.optimalTilingFeatures);
295 }
296 
297 void GrVkCaps::initStencilFormat(const GrVkInterface* interface, VkPhysicalDevice physDev) {
298     // List of legal stencil formats (though perhaps not supported on
299     // the particular gpu/driver) from most preferred to least. We are guaranteed to have either
300     // VK_FORMAT_D24_UNORM_S8_UINT or VK_FORMAT_D32_SFLOAT_S8_UINT. VK_FORMAT_D32_SFLOAT_S8_UINT
301     // can optionally have 24 unused bits at the end so we assume the total bits is 64.
302     static const StencilFormat
303                   // internal Format             stencil bits      total bits        packed?
304         gS8    = { VK_FORMAT_S8_UINT,            8,                 8,               false },
305         gD24S8 = { VK_FORMAT_D24_UNORM_S8_UINT,  8,                32,               true },
306         gD32S8 = { VK_FORMAT_D32_SFLOAT_S8_UINT, 8,                64,               true };
307 
308     if (stencil_format_supported(interface, physDev, VK_FORMAT_S8_UINT)) {
309         fPreferedStencilFormat = gS8;
310     } else if (stencil_format_supported(interface, physDev, VK_FORMAT_D24_UNORM_S8_UINT)) {
311         fPreferedStencilFormat = gD24S8;
312     } else {
313         SkASSERT(stencil_format_supported(interface, physDev, VK_FORMAT_D32_SFLOAT_S8_UINT));
314         fPreferedStencilFormat = gD32S8;
315     }
316 }
317 
318 void GrVkCaps::initConfigTable(const GrVkInterface* interface, VkPhysicalDevice physDev,
319                                const VkPhysicalDeviceProperties& properties) {
320     for (int i = 0; i < kGrPixelConfigCnt; ++i) {
321         VkFormat format;
322         if (GrPixelConfigToVkFormat(static_cast<GrPixelConfig>(i), &format)) {
323             if (!GrPixelConfigIsSRGB(static_cast<GrPixelConfig>(i)) || fSRGBSupport) {
324                 fConfigTable[i].init(interface, physDev, properties, format);
325             }
326         }
327     }
328 }
329 
330 void GrVkCaps::ConfigInfo::InitConfigFlags(VkFormatFeatureFlags vkFlags, uint16_t* flags) {
331     if (SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & vkFlags) &&
332         SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT & vkFlags)) {
333         *flags = *flags | kTextureable_Flag;
334 
335         // Ganesh assumes that all renderable surfaces are also texturable
336         if (SkToBool(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT & vkFlags)) {
337             *flags = *flags | kRenderable_Flag;
338         }
339     }
340 
341     if (SkToBool(VK_FORMAT_FEATURE_BLIT_SRC_BIT & vkFlags)) {
342         *flags = *flags | kBlitSrc_Flag;
343     }
344 
345     if (SkToBool(VK_FORMAT_FEATURE_BLIT_DST_BIT & vkFlags)) {
346         *flags = *flags | kBlitDst_Flag;
347     }
348 }
349 
350 void GrVkCaps::ConfigInfo::initSampleCounts(const GrVkInterface* interface,
351                                             VkPhysicalDevice physDev,
352                                             const VkPhysicalDeviceProperties& physProps,
353                                             VkFormat format) {
354     VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
355                               VK_IMAGE_USAGE_TRANSFER_DST_BIT |
356                               VK_IMAGE_USAGE_SAMPLED_BIT |
357                               VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
358     VkImageCreateFlags createFlags = GrVkFormatIsSRGB(format, nullptr)
359         ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0;
360     VkImageFormatProperties properties;
361     GR_VK_CALL(interface, GetPhysicalDeviceImageFormatProperties(physDev,
362                                                                  format,
363                                                                  VK_IMAGE_TYPE_2D,
364                                                                  VK_IMAGE_TILING_OPTIMAL,
365                                                                  usage,
366                                                                  createFlags,
367                                                                  &properties));
368     VkSampleCountFlags flags = properties.sampleCounts;
369     if (flags & VK_SAMPLE_COUNT_1_BIT) {
370         fColorSampleCounts.push(1);
371     }
372     if (kImagination_VkVendor == physProps.vendorID) {
373         // MSAA does not work on imagination
374         return;
375     }
376     if (flags & VK_SAMPLE_COUNT_2_BIT) {
377         fColorSampleCounts.push(2);
378     }
379     if (flags & VK_SAMPLE_COUNT_4_BIT) {
380         fColorSampleCounts.push(4);
381     }
382     if (flags & VK_SAMPLE_COUNT_8_BIT) {
383         fColorSampleCounts.push(8);
384     }
385     if (flags & VK_SAMPLE_COUNT_16_BIT) {
386         fColorSampleCounts.push(16);
387     }
388     if (flags & VK_SAMPLE_COUNT_32_BIT) {
389         fColorSampleCounts.push(32);
390     }
391     if (flags & VK_SAMPLE_COUNT_64_BIT) {
392         fColorSampleCounts.push(64);
393     }
394 }
395 
396 void GrVkCaps::ConfigInfo::init(const GrVkInterface* interface,
397                                 VkPhysicalDevice physDev,
398                                 const VkPhysicalDeviceProperties& properties,
399                                 VkFormat format) {
400     VkFormatProperties props;
401     memset(&props, 0, sizeof(VkFormatProperties));
402     GR_VK_CALL(interface, GetPhysicalDeviceFormatProperties(physDev, format, &props));
403     InitConfigFlags(props.linearTilingFeatures, &fLinearFlags);
404     InitConfigFlags(props.optimalTilingFeatures, &fOptimalFlags);
405     if (fOptimalFlags & kRenderable_Flag) {
406         this->initSampleCounts(interface, physDev, properties, format);
407     }
408 }
409 
410 int GrVkCaps::getRenderTargetSampleCount(int requestedCount, GrPixelConfig config) const {
411     requestedCount = SkTMax(1, requestedCount);
412     int count = fConfigTable[config].fColorSampleCounts.count();
413 
414     if (!count) {
415         return 0;
416     }
417 
418     if (1 == requestedCount) {
419         SkASSERT(fConfigTable[config].fColorSampleCounts.count() &&
420                  fConfigTable[config].fColorSampleCounts[0] == 1);
421         return 1;
422     }
423 
424     for (int i = 0; i < count; ++i) {
425         if (fConfigTable[config].fColorSampleCounts[i] >= requestedCount) {
426             return fConfigTable[config].fColorSampleCounts[i];
427         }
428     }
429     return 0;
430 }
431 
432 int GrVkCaps::maxRenderTargetSampleCount(GrPixelConfig config) const {
433     const auto& table = fConfigTable[config].fColorSampleCounts;
434     if (!table.count()) {
435         return 0;
436     }
437     return table[table.count() - 1];
438 }
439 
440 bool GrVkCaps::surfaceSupportsWritePixels(const GrSurface* surface) const {
441     if (auto rt = surface->asRenderTarget()) {
442         return rt->numColorSamples() <= 1 && SkToBool(surface->asTexture());
443     }
444     return true;
445 }
446 
447 bool validate_image_info(VkFormat format, SkColorType ct, GrPixelConfig* config) {
448     *config = kUnknown_GrPixelConfig;
449 
450     switch (ct) {
451         case kUnknown_SkColorType:
452             return false;
453         case kAlpha_8_SkColorType:
454             if (VK_FORMAT_R8_UNORM == format) {
455                 *config = kAlpha_8_as_Red_GrPixelConfig;
456             }
457             break;
458         case kRGB_565_SkColorType:
459             if (VK_FORMAT_R5G6B5_UNORM_PACK16 == format) {
460                 *config = kRGB_565_GrPixelConfig;
461             }
462             break;
463         case kARGB_4444_SkColorType:
464             if (VK_FORMAT_B4G4R4A4_UNORM_PACK16 == format) {
465                 *config = kRGBA_4444_GrPixelConfig;
466             }
467             break;
468         case kRGBA_8888_SkColorType:
469             if (VK_FORMAT_R8G8B8A8_UNORM == format) {
470                 *config = kRGBA_8888_GrPixelConfig;
471             } else if (VK_FORMAT_R8G8B8A8_SRGB == format) {
472                 *config = kSRGBA_8888_GrPixelConfig;
473             }
474             break;
475         case kRGB_888x_SkColorType:
476             return false;
477         case kBGRA_8888_SkColorType:
478             if (VK_FORMAT_B8G8R8A8_UNORM == format) {
479                 *config = kBGRA_8888_GrPixelConfig;
480             } else if (VK_FORMAT_B8G8R8A8_SRGB == format) {
481                 *config = kSBGRA_8888_GrPixelConfig;
482             }
483             break;
484         case kRGBA_1010102_SkColorType:
485             if (VK_FORMAT_A2B10G10R10_UNORM_PACK32 == format) {
486                 *config = kRGBA_1010102_GrPixelConfig;
487             }
488             break;
489         case kRGB_101010x_SkColorType:
490             return false;
491         case kGray_8_SkColorType:
492             if (VK_FORMAT_R8_UNORM == format) {
493                 *config = kGray_8_as_Red_GrPixelConfig;
494             }
495             break;
496         case kRGBA_F16_SkColorType:
497             if (VK_FORMAT_R16G16B16A16_SFLOAT == format) {
498                 *config = kRGBA_half_GrPixelConfig;
499             }
500             break;
501     }
502 
503     return kUnknown_GrPixelConfig != *config;
504 }
505 
506 bool GrVkCaps::validateBackendTexture(const GrBackendTexture& tex, SkColorType ct,
507                                       GrPixelConfig* config) const {
508     const GrVkImageInfo* imageInfo = tex.getVkImageInfo();
509     if (!imageInfo) {
510         return false;
511     }
512 
513     return validate_image_info(imageInfo->fFormat, ct, config);
514 }
515 
516 bool GrVkCaps::validateBackendRenderTarget(const GrBackendRenderTarget& rt, SkColorType ct,
517                                            GrPixelConfig* config) const {
518     const GrVkImageInfo* imageInfo = rt.getVkImageInfo();
519     if (!imageInfo) {
520         return false;
521     }
522 
523     return validate_image_info(imageInfo->fFormat, ct, config);
524 }
525 
526 bool GrVkCaps::getConfigFromBackendFormat(const GrBackendFormat& format, SkColorType ct,
527                                           GrPixelConfig* config) const {
528     const VkFormat* vkFormat = format.getVkFormat();
529     if (!vkFormat) {
530         return false;
531     }
532     return validate_image_info(*vkFormat, ct, config);
533 }
534 
535