File: stateless_validation_helper_generator.py

package info (click to toggle)
vulkan-validationlayers 1.4.321.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 47,412 kB
  • sloc: cpp: 594,175; python: 11,321; sh: 24; makefile: 20; xml: 14
file content (1124 lines) | stat: -rw-r--r-- 67,758 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
#!/usr/bin/python3 -i
#
# Copyright (c) 2015-2025 The Khronos Group Inc.
# Copyright (c) 2015-2025 Valve Corporation
# Copyright (c) 2015-2025 LunarG, Inc.
# Copyright (c) 2015-2025 Google Inc.
# Copyright (c) 2023-2025 RasterGrid Kft.
#
# 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.

import os
import re
from generators.generator_utils import buildListVUID, PlatformGuardHelper
from vulkan_object import Member, Struct
from base_generator import BaseGenerator

# This class is a container for any source code, data, or other behavior that is necessary to
# customize the generator script for a specific target API variant (e.g. Vulkan SC). As such,
# all of these API-specific interfaces and their use in the generator script are part of the
# contract between this repository and its downstream users. Changing or removing any of these
# interfaces or their use in the generator script will have downstream effects and thus
# should be avoided unless absolutely necessary.
class APISpecific:
    # Generates custom validation for a function parameter or returns None
    @staticmethod
    def genCustomValidation(targetApiName: str, funcName: str, member) -> list[str]:
        match targetApiName:

            # Vulkan specific custom validation (currently none)
            case 'vulkan':
                return None

class StatelessValidationHelperOutputGenerator(BaseGenerator):
    def __init__(self,
                 valid_usage_file):
        BaseGenerator.__init__(self)
        self.valid_vuids = buildListVUID(valid_usage_file)

        # These functions have additional, custom-written checks in the utils cpp file. CodeGen will automatically add a call
        # to those functions of the form 'bool manual_PreCallValidateAPIName', where the 'vk' is dropped.
        # see 'manual_PreCallValidateCreateGraphicsPipelines' as an example.
        self.functionsWithManualChecks = [
            'vkCreateDevice',
            'vkCreateQueryPool',
            'vkCreateRenderPass',
            'vkCreateRenderPass2',
            'vkCreateBuffer',
            'vkCreateImage',
            'vkCreateShaderModule',
            'vkCreatePipelineLayout',
            'vkCreateGraphicsPipelines',
            'vkCreateComputePipelines',
            'vkCreateRayTracingPipelinesNV',
            'vkCreateRayTracingPipelinesKHR',
            'vkCreateSampler',
            'vkCreateDescriptorSetLayout',
            'vkGetDescriptorSetLayoutSupport',
            'vkCreateBufferView',
            'vkCreateSemaphore',
            'vkCreateEvent',
            'vkFreeDescriptorSets',
            'vkUpdateDescriptorSets',
            'vkBeginCommandBuffer',
            'vkFreeCommandBuffers',
            'vkCmdSetViewport',
            'vkCmdSetScissor',
            'vkCmdSetLineWidth',
            'vkCmdClearAttachments',
            'vkCmdBindIndexBuffer',
            'vkCmdBindIndexBuffer2',
            'vkCmdCopyBuffer',
            'vkCmdUpdateBuffer',
            'vkCmdFillBuffer',
            'vkCreateSwapchainKHR',
            'vkCreateSharedSwapchainsKHR',
            'vkQueuePresentKHR',
            'vkCreateDescriptorPool',
            'vkCmdPushDescriptorSet',
            'vkCmdPushDescriptorSet2',
            'vkGetDescriptorEXT',
            'vkCmdSetDescriptorBufferOffsetsEXT',
            'vkCmdSetDescriptorBufferOffsets2EXT',
            'vkCmdBindDescriptorBufferEmbeddedSamplers2EXT',
            'vkCmdPushDescriptorSetWithTemplate2',
            'vkCmdBindDescriptorSets2',
            'vkCreateIndirectExecutionSetEXT',
            'vkCreateIndirectCommandsLayoutEXT',
            'vkCmdPreprocessGeneratedCommandsEXT',
            'vkCmdExecuteGeneratedCommandsEXT',
            'vkCmdSetExclusiveScissorNV',
            'vkCmdSetViewportShadingRatePaletteNV',
            'vkCmdSetCoarseSampleOrderNV',
            'vkAllocateMemory',
            'vkCreateAccelerationStructureNV',
            'vkCreateAccelerationStructureKHR',
            'vkDestroyAccelerationStructureKHR',
            'vkGetAccelerationStructureHandleNV',
            'vkGetPhysicalDeviceImageFormatProperties',
            'vkGetPhysicalDeviceImageFormatProperties2',
            'vkGetPhysicalDeviceProperties2',
            'vkCmdBuildAccelerationStructureNV',
            'vkCmdTraceRaysKHR',
            'vkCmdTraceRaysIndirectKHR',
            'vkCmdTraceRaysIndirect2KHR',
            'vkCreateFramebuffer',
            'vkCmdSetLineStipple',
            'vkSetDebugUtilsObjectNameEXT',
            'vkSetDebugUtilsObjectTagEXT',
            'vkCmdSetViewportWScalingNV',
            'vkCmdSetDepthClampRangeEXT',
            'vkAcquireNextImageKHR',
            'vkAcquireNextImage2KHR',
            'vkCmdBindTransformFeedbackBuffersEXT',
            'vkCmdBeginTransformFeedbackEXT',
            'vkCmdEndTransformFeedbackEXT',
            'vkCreateSamplerYcbcrConversion',
            'vkGetMemoryFdKHR',
            'vkImportSemaphoreFdKHR',
            'vkGetSemaphoreFdKHR',
            'vkImportFenceFdKHR',
            'vkGetFenceFdKHR',
            'vkGetMemoryWin32HandleKHR',
            'vkImportFenceWin32HandleKHR',
            'vkGetFenceWin32HandleKHR',
            'vkImportSemaphoreWin32HandleKHR',
            'vkGetSemaphoreWin32HandleKHR',
            'vkGetMemoryHostPointerPropertiesEXT',
            'vkCmdBindVertexBuffers',
            'vkCreateImageView',
            'vkCopyAccelerationStructureToMemoryKHR',
            'vkCmdCopyAccelerationStructureToMemoryKHR',
            'vkCopyAccelerationStructureKHR',
            'vkCmdCopyAccelerationStructureKHR',
            'vkCopyMemoryToAccelerationStructureKHR',
            'vkCmdCopyMemoryToAccelerationStructureKHR',
            'vkCmdWriteAccelerationStructuresPropertiesKHR',
            'vkWriteAccelerationStructuresPropertiesKHR',
            'vkGetRayTracingCaptureReplayShaderGroupHandlesKHR',
            'vkCmdBuildAccelerationStructureIndirectKHR',
            'vkGetDeviceAccelerationStructureCompatibilityKHR',
            'vkCmdSetViewportWithCount',
            'vkCmdSetScissorWithCount',
            'vkCmdBindVertexBuffers2',
            'vkCmdCopyBuffer2',
            'vkCmdPipelineBarrier2',
            'vkCmdSetEvent2',
            'vkCmdWaitEvents2',
            'vkCmdBuildAccelerationStructuresKHR',
            'vkCmdBuildAccelerationStructuresIndirectKHR',
            'vkBuildAccelerationStructuresKHR',
            'vkGetAccelerationStructureBuildSizesKHR',
            'vkCmdWriteAccelerationStructuresPropertiesNV',
            'vkCreateDisplayModeKHR',
            'vkCmdSetVertexInputEXT',
            'vkCmdPushConstants',
            'vkCmdPushConstants2',
            'vkCreatePipelineCache',
            'vkMergePipelineCaches',
            'vkCmdClearColorImage',
            'vkCmdBeginRenderPass',
            'vkCmdBeginRenderPass2',
            'vkCmdBeginRendering',
            'vkCmdSetDiscardRectangleEXT',
            'vkGetQueryPoolResults',
            'vkCmdBeginConditionalRenderingEXT',
            'vkGetDeviceImageMemoryRequirements',
            'vkGetDeviceImageSparseMemoryRequirements',
            'vkCreateAndroidSurfaceKHR',
            'vkCreateWin32SurfaceKHR',
            'vkCreateWaylandSurfaceKHR',
            'vkCreateXcbSurfaceKHR',
            'vkCreateXlibSurfaceKHR',
            'vkGetPhysicalDeviceSurfaceFormatsKHR',
            'vkGetPhysicalDeviceSurfacePresentModesKHR',
            'vkGetPhysicalDeviceSurfaceCapabilities2KHR',
            'vkGetPhysicalDeviceSurfaceFormats2KHR',
            'vkGetPhysicalDeviceSurfacePresentModes2EXT',
            'vkCmdSetDiscardRectangleEnableEXT',
            'vkCmdSetDiscardRectangleModeEXT',
            'vkCmdSetExclusiveScissorEnableNV',
            'vkGetMemoryWin32HandlePropertiesKHR',
            'vkGetMemoryFdPropertiesKHR',
            'vkCreateShadersEXT',
            'vkGetShaderBinaryDataEXT',
            'vkSetDeviceMemoryPriorityEXT',
            'vkGetDeviceImageSubresourceLayout',
            'vkQueueBindSparse',
            'vkCmdBindDescriptorBuffersEXT',
            'vkGetPhysicalDeviceExternalBufferProperties',
            'vkGetPipelinePropertiesEXT',
            'vkBuildMicromapsEXT',
            'vkCmdBuildMicromapsEXT',
            'vkCmdCopyMemoryToMicromapEXT',
            'vkCmdCopyMicromapEXT',
            'vkCmdCopyMicromapToMemoryEXT',
            'vkCmdWriteMicromapsPropertiesEXT',
            'vkCopyMemoryToMicromapEXT',
            'vkCopyMicromapEXT',
            'vkCopyMicromapToMemoryEXT',
            'vkCreateMicromapEXT',
            'vkDestroyMicromapEXT',
            'vkGetDeviceMicromapCompatibilityEXT',
            'vkGetMicromapBuildSizesEXT',
            'vkWriteMicromapsPropertiesEXT',
            'vkReleaseSwapchainImagesEXT',
            'vkReleaseSwapchainImagesKHR',
            'vkConvertCooperativeVectorMatrixNV',
            'vkCmdConvertCooperativeVectorMatrixNV',
        ]

        # Commands to ignore
        self.blacklist = [
            'vkGetInstanceProcAddr',
            'vkGetDeviceProcAddr',
            'vkEnumerateInstanceVersion',
            'vkEnumerateInstanceLayerProperties',
            'vkEnumerateInstanceExtensionProperties',
            'vkEnumerateDeviceLayerProperties',
            'vkEnumerateDeviceExtensionProperties',
            # All checking is manual for the below
            'vkGetDeviceGroupSurfacePresentModes2EXT',
            'vkCreateInstance',
            ]

        # Very rare case when structs are needed prior to setting up everything
        self.structsWithManualChecks = [
            'VkLayerSettingsCreateInfoEXT'
        ]

        # Validation conditions for some special case struct members that are conditionally validated
        self.structMemberValidationConditions = [
            {
                'struct' : 'VkSubpassDependency2',
                'field' :  'VkPipelineStageFlagBits',
                'condition' : '!vku::FindStructInPNextChain<VkMemoryBarrier2>(pCreateInfo->pDependencies[dependencyIndex].pNext)'
            },
            {
                'struct' : 'VkSubpassDependency2',
                'field' :  'VkAccessFlagBits',
                'condition' : '!vku::FindStructInPNextChain<VkMemoryBarrier2>(pCreateInfo->pDependencies[dependencyIndex].pNext)'
            }
        ]

        # Will create a validate function for the struct to be called by non-generated code.
        # There are cases where `noautovalidity` and `optional` are both true, but really are just
        # guarded by other conditions.
        #
        # Example: pViewportState should always be validated, when not ignored. The logic of when it
        # isn't ignored gets complex and best done by hand in sl_pipeline.cpp
        self.generateStructHelper = [
            # Graphic Pipeline states that can be ignored
            'VkPipelineViewportStateCreateInfo',
            'VkPipelineTessellationStateCreateInfo',
            'VkPipelineVertexInputStateCreateInfo',
            'VkPipelineMultisampleStateCreateInfo',
            'VkPipelineColorBlendStateCreateInfo',
            'VkPipelineDepthStencilStateCreateInfo',
            'VkPipelineInputAssemblyStateCreateInfo',
            'VkPipelineRasterizationStateCreateInfo',
            'VkPipelineShaderStageCreateInfo',

            # Ignored if not secondary command buffer
            'VkCommandBufferInheritanceInfo',

            # Uses an enum to decide which struct in a union to generate
            'VkDescriptorAddressInfoEXT',
            'VkAccelerationStructureGeometryTrianglesDataKHR',
            'VkAccelerationStructureGeometryInstancesDataKHR',
            'VkAccelerationStructureGeometryAabbsDataKHR',
            'VkIndirectExecutionSetPipelineInfoEXT', # VkIndirectExecutionSetShaderInfoEXT is done manually
        ]

        # These functions entrypoints we as VVL expose
        self.layerExtensionFunctions = [
            # VK_EXT_debug_utils
            'vkCmdBeginDebugUtilsLabelEXT',
            'vkCmdEndDebugUtilsLabelEXT',
            'vkCmdInsertDebugUtilsLabelEXT',
            'vkCreateDebugUtilsMessengerEXT',
            'vkDestroyDebugUtilsMessengerEXT',
            'vkQueueBeginDebugUtilsLabelEXT',
            'vkQueueEndDebugUtilsLabelEXT',
            'vkQueueInsertDebugUtilsLabelEXT',
            'vkSetDebugUtilsObjectNameEXT',
            'vkSetDebugUtilsObjectTagEXT',
            'vkSubmitDebugUtilsMessageEXT',
        ]

        # Map of structs type names to generated validation code for that struct type
        self.validatedStructs = dict()

        self.stype_version_dict = dict()

    def generate(self):
        self.write(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
            // See {os.path.basename(__file__)} for modifications

            /***************************************************************************
            *
            * Copyright (c) 2015-2025 The Khronos Group Inc.
            * Copyright (c) 2015-2025 Valve Corporation
            * Copyright (c) 2015-2025 LunarG, 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.
            ****************************************************************************/\n''')
        self.write('// NOLINTBEGIN') # Wrap for clang-tidy to ignore

        if self.filename == 'stateless_instance_methods.h':
            self.generateInstanceHeader()
        elif self.filename == 'stateless_device_methods.h':
            self.generateDeviceHeader()
        elif self.filename == 'stateless_validation_helper.cpp':
            self.generateSource()
        else:
            self.write(f'\nFile name {self.filename} has no code to generate\n')

        self.write('// NOLINTEND') # Wrap for clang-tidy to ignore

    def generateHeader(self, want_instance):
        out = []
        out.append('#pragma once\n')

        guard_helper = PlatformGuardHelper()
        for command in [x for x in self.vk.commands.values() if x.name not in self.blacklist and x.instance == want_instance]:
            out.extend(guard_helper.add_guard(command.protect))
            prototype = command.cPrototype.split('VKAPI_CALL ')[1]
            prototype = f'bool PreCallValidate{prototype[2:]}'
            prototype = prototype.replace(');', ') const override;\n')
            if 'ValidationCache' in command.name:
                prototype = prototype.replace('const override', 'const')
            prototype = prototype.replace(')', ',\n    const ErrorObject&                          error_obj)')
            out.append(prototype)
        out.extend(guard_helper.add_guard(None))
        self.write("".join(out))

    def generateInstanceHeader(self):
        self.generateHeader(True)

    def generateDeviceHeader(self):
        self.generateHeader(False)
        out = []
        for struct_name in self.generateStructHelper:
            out.append(f'bool Validate{struct_name[2:]}(const Context &context, const {struct_name} &info, const Location &loc) const;')

        self.write("".join(out))

    def generateSource(self):
        # Structure fields to ignore
        structMemberBlacklist = {
            'VkWriteDescriptorSet' : ['dstSet'],
            'VkAccelerationStructureGeometryKHR' :['geometry'],
            'VkDescriptorDataEXT' :['pSampler'],
            # https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9887
            'VkClusterAccelerationStructureInputInfoNV' :['opInput'],
        }
        for struct in [x for x in self.vk.structs.values() if x.name in structMemberBlacklist]:
            for member in [x for x in struct.members if x.name in structMemberBlacklist[struct.name]]:
                member.noAutoValidity = True

        # TODO - We should not need this with VulkanObject, but the following are casuing issues
        # being "promoted"
        #  VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT
        #  VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_2_PLANE_444_FORMATS_FEATURES_EXT
        #  VK_STRUCTURE_TYPE_QUEUE_FAMILY_CHECKPOINT_PROPERTIES_2_NV
        #  VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT
        #  VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT
        #  VK_STRUCTURE_TYPE_CHECKPOINT_DATA_2_NV
        #  VK_STRUCTURE_TYPE_MULTIVIEW_PER_VIEW_ATTRIBUTES_INFO_NVX
        #  VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR
        #  VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT
        #  VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_DENSITY_MAP_ATTACHMENT_INFO_EXT
        root = self.registry.reg

        extToPromotedExtDict = dict()
        for extensions in root.findall('extensions'):
            for extension in extensions.findall('extension'):
                extension_name = extension.get('name')
                if extension_name not in extToPromotedExtDict.keys():
                    extToPromotedExtDict[extension_name] = set()
                promotedTo = extension.get('promotedto')
                if promotedTo is not None:
                    extToPromotedExtDict[extension_name] = promotedTo
                else:
                    extToPromotedExtDict[extension_name] = None

        for extensions in root.findall('extensions'):
            for extension in extensions.findall('extension'):
                extension_name = extension.get('name')
                promoted_ext = extToPromotedExtDict[extension_name]
                while promoted_ext is not None and 'VK_VERSION' not in promoted_ext:
                    promoted_ext = extToPromotedExtDict[promoted_ext]
                # TODO Issue 5103 - this is being used to remove false positive currently
                promoted_to_core = promoted_ext is not None and 'VK_VERSION' in promoted_ext

                for entry in extension.iterfind('require/enum[@extends="VkStructureType"]'):
                    if (entry.get('comment') is None or 'typo' not in entry.get('comment')):
                        alias = entry.get('alias')
                        if (alias is not None and promoted_to_core):
                            if (alias not in self.stype_version_dict.keys()):
                                self.stype_version_dict[alias] = set()
                            self.stype_version_dict[alias].add(extension_name)
                            self.stype_version_dict[alias].add(extension_name)

        # Generate the struct member checking code from the captured data
        for struct in self.vk.structs.values():
            # The string returned will be nested in an if check for a NULL pointer, so needs its indent incremented
            lines = self.genFuncBody(self.vk.structs[struct.name].members, '{funcName}', '{errorLoc}', '{valuePrefix}', '{displayNamePrefix}', struct.name, '{context}')
            if lines:
                self.validatedStructs[struct.name] = lines

        out = []
        out.append('''
            #include "stateless/stateless_validation.h"
            #include "generated/enum_flag_bits.h"
            #include "generated/dispatch_functions.h"

            namespace stateless {
            ''')
        out.append('\nbool Context::IsDuplicatePnext(VkStructureType input_value) const {\n')
        out.append('    switch (input_value) {\n')
        for struct in [x for x in self.vk.structs.values() if x.allowDuplicate and x.sType is not None]:
            # The sType will always be first member of struct
            out.append(f'        case {struct.sType}:\n')
        out.append('            return true;\n')
        out.append('        default:\n')
        out.append('            return false;\n')
        out.append('    }\n')
        out.append('}\n')
        out.append('\n')

        # The reason we split this up into Feature and Properties struct is before be had a 450 case, 10k line function that broke MSVC
        # reference: https://www.asawicki.info/news_1617_how_code_refactoring_can_fix_stack_overflow_error
        extended_structs = [x for x in self.vk.structs.values() if x.extends]
        feature_structs = [x for x in extended_structs if x.extends == ["VkPhysicalDeviceFeatures2", "VkDeviceCreateInfo"]]
        # NOTE: property structs are no longer checked. Previously we only checked if the corresponding extension was supported
        # by the physical device. Implementations are supposed to ignore unknown pNext values and it is low consequence to query
        # for unknown properties. https://github.com/KhronosGroup/Vulkan-ValidationLayers/pull/9302
        property_structs = [x for x in extended_structs if x.extends == ["VkPhysicalDeviceProperties2"]]
        other_structs = [x for x in extended_structs if x not in feature_structs and x not in property_structs and x.name not in self.structsWithManualChecks]

        out.append('''
            bool Context::ValidatePnextFeatureStructContents(const Location& loc,
                                                             const VkBaseOutStructure* header, const char *pnext_vuid,
                                                             bool is_const_param) const {
                bool skip = false;
                switch(header->sType) {
            ''')
        guard_helper = PlatformGuardHelper()
        for struct in feature_structs:
            out.extend(guard_helper.add_guard(struct.protect))
            out.extend(self.genStructBody(struct, False, ''))
        out.extend(guard_helper.add_guard(None))
        out.append('''
                    default:
                        skip = false;
                }
                return skip;
            }

            ''')

        out.append('''
            // All structs that are not a Feature or Property struct
            bool Context::ValidatePnextStructContents(const Location& loc,
                                                      const VkBaseOutStructure* header, const char *pnext_vuid,
                                                      bool is_const_param) const {
                bool skip = false;
                switch(header->sType) {
            ''')
        guard_helper = PlatformGuardHelper()
        for struct in other_structs:
            out.extend(guard_helper.add_guard(struct.protect))
            out.extend(self.genStructBody(struct, True, ''))
        out.extend(guard_helper.add_guard(None))
        out.append('''
                    default:
                        skip = false;
                }
                return skip;
            }

            ''')

        # Some extensions are alias from EXT->KHR but are not promoted, example
        #   vkGetImageSubresourceLayout2EXT (VK_EXT_host_image_copy)
        #   vkGetImageSubresourceLayout2KHR (VK_KHR_maintenance5)
        alias_but_not_core = []
        for command in [x for x in self.vk.commands.values() if x.name not in self.blacklist and x.alias and x.alias in self.vk.commands]:
            aliasCommand = self.vk.commands[command.alias]
            if aliasCommand.version is None:
                alias_but_not_core.append(aliasCommand.name)

        # Generate the command parameter checking code from the captured data
        for command in [x for x in self.vk.commands.values() if x.name not in self.blacklist]:
            out.extend(guard_helper.add_guard(command.protect, extra_newline=True))

            prototype = (command.cPrototype.split('VKAPI_CALL ')[1])[2:-1]
            prototype = prototype.replace(')', ', const ErrorObject& error_obj)')
            classname = 'Instance' if command.instance else 'Device'
            out.append(f'bool {classname}::PreCallValidate{prototype} const {{\n')
            out.append('    bool skip = false;\n')

            # Temporarily hold on, if there is no validation, will discard
            context = ''

            # For vkCreateDevice, the extensions member has already been set up properly
            # for other VkPhysicalDevice calls, we need to use their supported extensions rather
            # than the extensions members, which is how the VkInstance was configured.
            if command.params[0].type == 'VkPhysicalDevice' and command.name != 'vkCreateDevice':
                context = '''
                    const auto &physdev_extensions = physical_device_extensions.at(physicalDevice);
                    Context context(*this, error_obj, physdev_extensions, IsExtEnabled(physdev_extensions.vk_khr_maintenance5));
                '''
            else:
                context = '    Context context(*this, error_obj, extensions);\n'

            # Create a copy here to make the logic simpler passing into ValidatePnextStructContents
            location = '    [[maybe_unused]] const Location loc = error_obj.location;\n'

            functionBody = []
            # Cannot validate extension dependencies for device extension APIs having a physical device as their dispatchable object
            if command.extensions and command.name not in self.layerExtensionFunctions and (not any(self.vk.extensions[x].device for x in command.extensions) or command.params[0].type != 'VkPhysicalDevice'):
                cExpression =  []
                outExpression =  []
                for extension in command.extensions:
                    outExpression.append(f'vvl::Extension::_{extension}')
                    cExpression.append(f'IsExtEnabled(extensions.{extension.lower()})')

                cExpression = " || ".join(cExpression)
                if len(outExpression) > 1:
                    cExpression = f'({cExpression})'

                if command.name in alias_but_not_core:
                    cExpression += f' && loc.function == vvl::Func::{command.name}'
                functionBody.append(f'if (!{cExpression}) skip |= OutputExtensionError(loc, {{{", ".join(outExpression)}}});\n')

            if command.alias and command.alias in self.vk.commands:
                # For alias that are promoted, just point to new function, ErrorObject will allow us to distinguish the caller
                # Note that we can only do this if the promoted version is part of the target API
                paramList = [param.name for param in command.params]
                paramList.append('error_obj')
                params = ', '.join(paramList)
                functionBody.append(f'skip |= PreCallValidate{command.alias[2:]}({params});')
            else:
                # Skip first parameter if it is a dispatch handle (everything except vkCreateInstance)
                startIndex = 0 if command.name == 'vkCreateInstance' else 1
                lines = self.genFuncBody(command.params[startIndex:], command.name, 'loc', '', '', None, 'context.')

                if command.instance and command.version:
                    # check function name so KHR version doesn't trigger flase positive
                    functionBody.append(f'if (loc.function == vvl::Func::{command.name} && CheckPromotedApiAgainstVulkanVersion({command.params[0].name}, loc, {command.version.nameApi})) return true;\n')

                if not command.allowNoQueues and command.params[0].type == 'VkDevice':
                    if 'vkCreate' in command.name or 'vkAllocate' in command.name:
                        functionBody.append(f'''
                            if (has_zero_queues) {{
                                skip |= LogError("VUID-{command.name}-device-queuecount", device, error_obj.location, "device was created with queueCreateInfoCount of zero.");
                            }}
                            ''')

                for line in lines:
                    if isinstance(line, list):
                        for sub in line:
                            functionBody.append(sub)
                    else:
                        functionBody.append(line)
                # Insert call to custom-written function if present
                if command.name in self.functionsWithManualChecks:
                    manualCheckCmd = command.name
                # We also have to consider aliases here as the promoted version may not be part of the target API
                elif command.alias in self.functionsWithManualChecks:
                    manualCheckCmd = command.alias
                else:
                    manualCheckCmd = None
                if manualCheckCmd:
                    # Generate parameter list for manual fcn and down-chain calls
                    params_text = ', '.join([x.name for x in command.params]) + ', context'
                    functionBody.append(f'    if (!skip) skip |= manual_PreCallValidate{manualCheckCmd[2:]}({params_text});\n')

            # Only apply if there is actually validation
            if functionBody:
                # Will remove a few exta declartion of the Context when not needed
                if len(functionBody) > 1 or 'context' in functionBody[0]:
                    out.append(context)
                if len(functionBody) > 1 or 'loc' in functionBody[0]:
                    out.append(location)
                out.extend(functionBody)

            out.append('return skip;\n')
            out.append('}\n')
        out.extend(guard_helper.add_guard(None, extra_newline=True))

        for struct_name in self.generateStructHelper:
            out.append(f'bool Device::Validate{struct_name[2:]}(const Context &context, const {struct_name} &info, const Location &loc) const {{\n')
            out.append('    bool skip = false;\n')
            # Only generate validation code if the structure actually exists in the target API
            if struct_name in self.vk.structs:
                out.extend(self.expandStructCode(struct_name, struct_name, 'loc', 'info.', '', [], 'context.'))
            out.append('    return skip;\n')
            out.append('}\n')

        out.append('''
            } // namespace stateless
        ''')
        self.write("".join(out))

    def isHandleOptional(self, member: Member, lengthMember: Member) -> bool :
        # Simple, if it's optional, return true
        if member.optional or member.optionalPointer:
            return True
        # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes.
        if member.noAutoValidity:
            return True
        # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional
        if lengthMember and lengthMember.optional:
            return True
        return

    def isFlagReserved(self, flag) -> bool:
        bitmask = self.vk.flags[flag].bitmaskName
        # Check if doesn't have an associated bitmask type, or if the associated bitmask type is empty
        if bitmask is None or len(self.vk.bitmasks[bitmask].flags) == 0:
            return True
        return False

    # Get VUID identifier from implicit VUID tag
    def GetVuid(self, name, suffix):
        vuid_string = f'VUID-{name}-{suffix}'
        vuid = "kVUIDUndefined"
        if '->' in vuid_string:
           return vuid
        if vuid_string in self.valid_vuids:
            vuid = f'"{vuid_string}"'
        elif name in self.vk.commands:
            # Only commands have alias to worry about
            alias_string = f'VUID-{self.vk.commands[name].alias}-{suffix}'
            if alias_string in self.valid_vuids:
                vuid = f'"{alias_string}"'
        return vuid

    # Generate the pointer check string
    def makePointerCheck(self, valuePrefix, member: Member, lengthMember: Member, errorLoc, arrayRequired, counValueRequired, counPtrRequired, funcName, structTypeName, context):
        checkExpr = []
        callerName = structTypeName if structTypeName else funcName
        if lengthMember:
            length_deref = '->' in member.length
            countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength")
            arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter")
            countPtrRequiredVuid = self.GetVuid(callerName, f"{member.length}-parameter")
            # This is an array with a pointer to a count value
            if lengthMember.pointer and not length_deref:
                # If count and array parameters are optional, there will be no validation
                if arrayRequired == 'true' or counPtrRequired == 'true' or counValueRequired == 'true':
                    # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
                    checkExpr.append(f'skip |= {context}ValidatePointerArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, &{valuePrefix}{member.name}, {counPtrRequired}, {counValueRequired}, {arrayRequired},{countPtrRequiredVuid}, {countRequiredVuid}, {arrayRequiredVuid});\n')
            # This is an array with an integer count value
            else:
                # Can't check if a non-null pointer is a valid pointer in a layer
                unimplementable = member.optional and (lengthMember.optional or lengthMember.optionalPointer)
                # If count and array parameters are optional, there will be no validation
                if (arrayRequired == 'true' or counValueRequired == 'true') and not unimplementable:
                    if member.type == 'char':
                        # Arrays of strings receive special processing
                        checkExpr.append(f'skip |= {context}ValidateStringArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n')
                    else:
                        # A valid VU can't use '->' in the middle so the generated VUID from the spec uses '::' instead
                        countRequiredVuid = self.GetVuid(callerName, f"{member.length.replace('->', '::')}-arraylength")
                        if structTypeName == 'VkShaderModuleCreateInfo' and member.name == 'pCode':
                            countRequiredVuid = '"VUID-VkShaderModuleCreateInfo-codeSize-01085"' # exception due to unique lenValue

                        # TODO - some length have unhandled symbols
                        count_loc = f'{errorLoc}.dot(Field::{member.length})'
                        if '->' in member.length:
                            count_loc = f'{errorLoc}.dot(Field::{member.length.split("->")[0]}).dot(Field::{member.length.split("->")[1]})'
                        elif ' + ' in member.length:
                            # hardcoded only instance for now
                            if 'samples' in member.length: # "(samples + 31) / 32"
                                count_loc = f'{errorLoc}.dot(Field::samples)'
                            elif 'rasterizationSamples' in member.length: # "(rasterizationSamples + 31) / 32"
                                count_loc = f'{errorLoc}.dot(Field::rasterizationSamples)'
                                member.length = 'rasterizationSamples'
                        elif ' / ' in member.length:
                            count_loc = f'{errorLoc}.dot(Field::{member.length.split(" / ")[0]})'
                        checkExpr.append(f'skip |= {context}ValidateArray({count_loc}, {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, &{valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n')
            if checkExpr and lengthMember and length_deref and member.length.count('->'):
                # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count
                count = member.length.count('->')
                checkedExpr = []
                elements = member.length.split('->')
                # Open the if expression blocks
                for i in range(0, count):
                    checkedExpr.append(f'if ({"->".join(elements[0:i+1])} != nullptr) {{\n')
                # Add the validation expression
                for expr in checkExpr:
                    checkedExpr.append(expr)
                # Close the if blocks
                for i in range(0, count):
                    checkedExpr.append('}\n')
                checkExpr = [checkedExpr]
        # This is an individual struct that is not allowed to be NULL
        elif not (member.optional or member.fixedSizeArray):
            # Function pointers need a reinterpret_cast to void*
            ptrRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter")
            if member.type.startswith('PFN_'):
                checkExpr.append(f'skip |= {context}ValidateRequiredPointer({errorLoc}.dot(Field::{member.name}), reinterpret_cast<const void*>({valuePrefix}{member.name}), {ptrRequiredVuid});\n')
            else:
                checkExpr.append(f'skip |= {context}ValidateRequiredPointer({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name}, {ptrRequiredVuid});\n')
        return checkExpr

    # Process struct member validation code, performing name substitution if required
    def processStructMemberCode(self, line, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix, context):
        # Build format specifier list
        kwargs = {}
        if '{funcName}' in line:
            kwargs['funcName'] = funcName
        if '{errorLoc}' in line:
            kwargs['errorLoc'] = errorLoc
        if '{valuePrefix}' in line:
            kwargs['valuePrefix'] = memberNamePrefix
        if '{displayNamePrefix}' in line:
            # Check for a tuple that includes a format string and format parameters to be used with the ParameterName class
            if type(memberDisplayNamePrefix) is tuple:
                kwargs['displayNamePrefix'] = memberDisplayNamePrefix[0]
            else:
                kwargs['displayNamePrefix'] = memberDisplayNamePrefix
        if '{context}' in line:
                kwargs['context'] = context

        if kwargs:
            # Need to escape the C++ curly braces
            return line.format(**kwargs)
        return line

    # Process struct member validation code, stripping metadata
    def ScrubStructCode(self, code):
        scrubbed_lines = ''
        for line in code:
            if 'xml-driven validation' in line:
                continue
            line = line.replace('{funcName}', '')
            line = line.replace('{errorLoc}', '')
            line = line.replace('{valuePrefix}', '')
            line = line.replace('{displayNamePrefix}', '')
            scrubbed_lines += line
        return scrubbed_lines

    # Process struct validation code for inclusion in function or parent struct validation code
    def expandStructCode(self, item_type, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix, output, context):
        if item_type not in self.validatedStructs:
            return ""
        lines = self.validatedStructs[item_type]
        for line in lines:
            if output:
                output[-1] += '\n'
            if isinstance(line, list):
                for sub in line:
                    output.append(self.processStructMemberCode(sub, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix, context))
            else:
                output.append(self.processStructMemberCode(line, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix, context))
        return output

    # Generate the parameter checking code
    def genFuncBody(self, members: list[Member], funcName, errorLoc, valuePrefix, displayNamePrefix, structTypeName, context):
        struct = self.vk.structs[structTypeName] if structTypeName in self.vk.structs else None
        callerName = structTypeName if structTypeName else funcName
        lines = []    # Generated lines of code
        duplicateCountVuid = [] # prevent duplicate VUs being generated

        # TODO Using a regex in this context is not ideal. Would be nicer if usedLines were a list of objects with "settings"
        validatePNextRegex = re.compile(r'(.*ValidateStructPnext\(.*)(\).*\n*)', re.M)

        # Special struct since lots of functions have this, but it can be all combined to the same call (since it is always from the top level of a funciton)
        if structTypeName == 'VkAllocationCallbacks' :
            lines.append(f'skip |= {context}ValidateAllocationCallbacks(*pAllocator, pAllocator_loc);')
            return lines

        # Returnedonly structs should have most of their members ignored -- on entry, we only care about validating the sType and
        # pNext members. Everything else will be overwritten by the callee.
        for member in [x for x in members if not struct or not struct.returnedOnly or (x.name in ('sType', 'pNext'))]:
            usedLines = []
            lengthMember = None
            condition = None
            #
            # Generate the full name of the value, which will be printed in the error message, by adding the variable prefix to the value name
            valueDisplayName = f'{displayNamePrefix}{member.name}'
            #
            # Check for NULL pointers, ignore the in-out count parameters that
            # will be validated with their associated array
            if (member.pointer or member.fixedSizeArray) and not [x for x in members if x.length and member.name == x.length]:
                # Parameters for function argument generation
                arrayRequired = 'true'    # Parameter cannot be NULL
                counPtrRequired = 'true'  # Count pointer cannot be NULL
                counValueRequired = 'true'  # Count value cannot be 0
                countRequiredVuid = None # If there is a count required VUID to check
                # Generate required/optional parameter strings for the pointer and count values
                if member.optional or member.optionalPointer:
                    arrayRequired = 'false'
                if member.length:
                    # The parameter is an array with an explicit count parameter
                    # Find a named parameter in a parameter list
                    lengthMember = next((x for x in members if x.name == member.length), None)

                    # First check if any element of params matches length exactly
                    if not lengthMember:
                        # Otherwise, look for any elements of params that appear within length
                        candidates = [p for p in members if re.search(r'\b{}\b'.format(p.name), member.length)]
                        # 0 or 1 matches are expected, >1 would require a special case and/or explicit validation
                        if len(candidates) == 0:
                            lengthMember = None
                        elif len(candidates) == 1:
                            lengthMember = candidates[0]

                    if lengthMember:
                        if lengthMember.pointer:
                            counPtrRequired = 'false' if lengthMember.optional else counPtrRequired
                            counValueRequired = 'false' if lengthMember.optionalPointer else counValueRequired
                            # In case of count as field in another struct, look up field to see if count is optional.
                            len_deref = member.length.split('->')
                            if len(len_deref) == 2:
                                lenMembers = next((x.members for x in self.vk.structs.values() if x.name == lengthMember.type), None)
                                if lenMembers and next((x for x in lenMembers if x.name == len_deref[1] and x.optional), None):
                                    counValueRequired = 'false'
                        else:
                            vuidName = self.GetVuid(callerName, f"{lengthMember.name}-arraylength")
                            # This VUID is considered special, as it is the only one whose names ends in "-arraylength" but has special conditions allowing bindingCount to be 0.
                            arrayVuidExceptions = ['"VUID-vkCmdBindVertexBuffers2-bindingCount-arraylength"']
                            if vuidName in arrayVuidExceptions:
                                continue
                            if lengthMember.optional:
                                counValueRequired = 'false'
                            elif member.noAutoValidity:
                                # Handle edge case where XML expresses a non-optional non-pointer value length with noautovalidity
                                # ex: <param noautovalidity="true"len="commandBufferCount">
                                countRequiredVuid = self.GetVuid(callerName, f"{lengthMember.name}-arraylength")
                                if countRequiredVuid in duplicateCountVuid:
                                    countRequiredVuid = None
                                else:
                                    duplicateCountVuid.append(countRequiredVuid)
                    else:
                        # Do not generate length checks for constant sized arrays
                        counPtrRequired = 'false'
                        counValueRequired = 'false'

                #
                # The parameter will not be processed when tagged as 'noautovalidity'
                # For the pointer to struct case, the struct pointer will not be validated, but any
                # members not tagged as 'noautovalidity' will be validated
                # We special-case the custom allocator checks, as they are explicit but can be auto-generated.
                AllocatorFunctions = ['PFN_vkAllocationFunction', 'PFN_vkReallocationFunction', 'PFN_vkFreeFunction', 'PFN_vkInternalAllocationNotification', 'PFN_vkInternalFreeNotification']
                apiSpecificCustomValidation = APISpecific.genCustomValidation(self.targetApiName, funcName, member)
                if apiSpecificCustomValidation is not None:
                    usedLines.extend(apiSpecificCustomValidation)
                elif member.noAutoValidity and member.type not in AllocatorFunctions and not countRequiredVuid:
                    # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually
                    self.logMsg('diag', f'ParameterValidation: No validation for {callerName} {member.name}')
                elif countRequiredVuid:
                    usedLines.append(f'skip |= {context}ValidateArray({errorLoc}.dot(Field::{member.length}), loc, {valuePrefix}{member.length}, &{valuePrefix}{member.name}, true, false, {countRequiredVuid}, kVUIDUndefined);\n')
                else:
                    if member.type in self.vk.structs and self.vk.structs[member.type].sType:
                        # If this is a pointer to a struct with an sType field, verify the type
                        struct = self.vk.structs[member.type]
                        sTypeVuid = self.GetVuid(member.type, "sType-sType")
                        paramVuid = self.GetVuid(callerName, f"{member.name}-parameter")
                        if lengthMember:
                            countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength")
                            # There is no way to test checking a non-null pointer to be valid, so don't falsly print the VUID out
                            if paramVuid != 'kVUIDUndefined' and arrayRequired == 'false':
                                paramVuid = 'kVUIDUndefined'
                            # This is an array of struct pointers
                            if member.cDeclaration.count('*') == 2:
                                usedLines.append(f'skip |= {context}ValidateStructPointerTypeArray({errorLoc}.dot(Field::{lengthMember.name}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{lengthMember.name}, {valuePrefix}{member.name}, {struct.sType}, {counValueRequired}, {arrayRequired}, {sTypeVuid}, {paramVuid}, {countRequiredVuid});\n')
                            # This is an array with a pointer to a count value
                            elif lengthMember.pointer:
                                # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
                                countPtrRequiredVuid = self.GetVuid(callerName, f"{member.length}-parameter")
                                usedLines.append(f'skip |= {context}ValidateStructTypeArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {struct.sType}, {counPtrRequired}, {counValueRequired}, {arrayRequired}, {sTypeVuid}, {paramVuid}, {countPtrRequiredVuid}, {countRequiredVuid});\n')
                            # This is an array with an integer count value
                            else:
                                usedLines.append(f'skip |= {context}ValidateStructTypeArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {struct.sType}, {counValueRequired}, {arrayRequired}, {sTypeVuid}, {paramVuid}, {countRequiredVuid});\n')
                        # This is an individual struct
                        else:
                            usedLines.append(f'skip |= {context}ValidateStructType({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name}, {struct.sType}, {arrayRequired}, {paramVuid}, {sTypeVuid});\n')
                    # If this is an input handle array that is not allowed to contain NULL handles, verify that none of the handles are VK_NULL_HANDLE
                    elif member.type in self.vk.handles and member.const and not self.isHandleOptional(member, lengthMember):
                        if not lengthMember:
                            # This is assumed to be an output handle pointer
                            raise Exception('Unsupported parameter validation case: Output handles are not NULL checked')
                        elif lengthMember.pointer:
                            # This is assumed to be an output array with a pointer to a count value
                            raise Exception('Unsupported parameter validation case: Output handle array elements are not NULL checked')
                        countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength")
                        # This is an array with an integer count value
                        usedLines.append(f'skip |= {context}ValidateHandleArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid});\n')
                    elif member.type in self.vk.flags and member.const:
                        # Generate check string for an array of VkFlags values
                        if self.isFlagReserved(member.type):
                            raise Exception('Unsupported parameter validation case: array of reserved VkFlags')
                        flagBitsName = self.vk.flags[member.type].bitmaskName
                        allFlags = 'All' + flagBitsName
                        countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength")
                        arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter")
                        usedLines.append(f'skip |= {context}ValidateFlagsArray({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), vvl::FlagBitmask::{flagBitsName}, {allFlags}, {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n')
                    elif member.type == 'VkBool32' and member.const:
                        countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength")
                        arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter")
                        usedLines.append(f'skip |= {context}ValidateBool32Array({errorLoc}.dot(Field::{member.length}), {errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n')
                    elif member.type in self.vk.enums and member.const:
                        lenLoc = 'loc' if member.fixedSizeArray else f'{errorLoc}.dot(Field::{member.length})'
                        countRequiredVuid = self.GetVuid(callerName, f"{member.length}-arraylength")
                        arrayRequiredVuid = self.GetVuid(callerName, f"{member.name}-parameter")
                        usedLines.append(f'skip |= {context}ValidateRangedEnumArray({lenLoc}, {errorLoc}.dot(Field::{member.name}), vvl::Enum::{member.type}, {valuePrefix}{member.length}, {valuePrefix}{member.name}, {counValueRequired}, {arrayRequired}, {countRequiredVuid}, {arrayRequiredVuid});\n')
                    elif member.name == 'pNext':
                        # Generate an array of acceptable VkStructureType values for pNext
                        allowedTypeCount = 0
                        allowedTypes = 'nullptr'
                        pNextVuid = self.GetVuid(structTypeName, "pNext-pNext")
                        sTypeVuid = self.GetVuid(structTypeName, "sType-unique")
                        # If no VUIDs we will only be potentially giving false positives
                        if pNextVuid != 'kVUIDUndefined' or sTypeVuid != 'kVUIDUndefined':
                            struct = self.vk.structs[structTypeName]
                            if struct.extendedBy:
                                allowedStructName = f'allowed_structs_{structTypeName}'
                                allowedTypeCount = f'{allowedStructName}.size()'
                                allowedTypes = f'{allowedStructName}.data()'
                                extendedBy = ", ".join([self.vk.structs[x].sType for x in struct.extendedBy])
                                usedLines.append(f'constexpr std::array {allowedStructName} = {{ {extendedBy} }};\n')

                            usedLines.append(f'skip |= {context}ValidateStructPnext({errorLoc}, {valuePrefix}{member.name}, {allowedTypeCount}, {allowedTypes}, GeneratedVulkanHeaderVersion, {pNextVuid}, {sTypeVuid});\n')
                    else:
                        usedLines += self.makePointerCheck(valuePrefix, member, lengthMember, errorLoc, arrayRequired, counValueRequired, counPtrRequired, funcName, structTypeName, context)

                    # Feature structs are large and just wasting time checking booleans that are almost impossible to be invalid
                    isFeatureStruct = member.type in ['VkPhysicalDeviceFeatures', 'VkPhysicalDeviceFeatures2']
                    # If this is a pointer to a struct (input), see if it contains members that need to be checked
                    if member.type in self.validatedStructs and (member.const or self.vk.structs[member.type].returnedOnly or member.pointer) and not isFeatureStruct:
                        # Process struct pointer/array validation code, performing name substitution if required
                        expr = []
                        expr.append(f'if ({valuePrefix}{member.name} != nullptr)\n')
                        expr.append('{\n')
                        newErrorLoc = f'{member.name}_loc'
                        if lengthMember:
                            # Need to process all elements in the array
                            length = member.length.split(',')[0]
                            indexName = length.replace('Count', 'Index')
                            # If the length value is a pointer, de-reference it for the count.
                            deref = '*' if lengthMember.pointer else ''
                            expr.append(f'for (uint32_t {indexName} = 0; {indexName} < {deref}{valuePrefix}{length}; ++{indexName})\n')
                            expr.append('{\n')
                            expr.append(f'[[maybe_unused]] const Location {newErrorLoc} = {errorLoc}.dot(Field::{member.name}, {indexName});')
                            # Prefix for value name to display in error message
                            connector = '->' if member.cDeclaration.count('*') == 2 else '.'
                            memberNamePrefix = f'{valuePrefix}{member.name}[{indexName}]{connector}'
                            memberDisplayNamePrefix = (f'{valueDisplayName}[%i]{connector}', indexName)
                        else:
                            expr.append(f'[[maybe_unused]] const Location {newErrorLoc} = {errorLoc}.dot(Field::{member.name});')
                            memberNamePrefix = f'{valuePrefix}{member.name}->'
                            memberDisplayNamePrefix = f'{valueDisplayName}->'

                        # Expand the struct validation lines
                        expr = self.expandStructCode(member.type, funcName, newErrorLoc, memberNamePrefix, memberDisplayNamePrefix, expr, context)
                        # If only 4 lines and no "skip" then this is an empty check
                        hasChecks = len(expr) > 4 or 'skip' in expr[3]
                        hasChecks = hasChecks if member.type != 'VkRect2D' else False # exception that doesn't have check actually

                        if lengthMember:
                            expr.append('}\n')
                        expr.append('}\n')

                        if hasChecks:
                            usedLines.append(expr)

                    isConstStr = 'true' if member.const else 'false'
                    for setter, _, elem in multi_string_iter(usedLines):
                        elem = re.sub(r', (true|false|physicalDevice|VK_NULL_HANDLE)', '', elem)
                        m = validatePNextRegex.match(elem)
                        if m is not None:
                            setter(f'{m.group(1)}, {isConstStr}{m.group(2)}')

            # Non-pointer types
            else:
                # The parameter will not be processes when tagged as 'noautovalidity'
                # For the struct case, the struct type will not be validated, but any
                # members not tagged as 'noautovalidity' will be validated
                if member.noAutoValidity:
                    # Log a diagnostic message when validation cannot be automatically generated and must be implemented manually
                    self.logMsg('diag', f'ParameterValidation: No validation for {callerName} {member.name}')
                else:
                    if member.type in self.vk.structs and self.vk.structs[member.type].sType:
                        sTypeVuid = self.GetVuid(member.type, "sType-sType")
                        sType = self.vk.structs[member.type].sType
                        usedLines.append(f'skip |= {context}ValidateStructType({errorLoc}.dot(Field::{member.name}), &({valuePrefix}{member.name}), {sType}, false, kVUIDUndefined, {sTypeVuid});\n')
                    elif member.name == 'sType' and structTypeName in self.generateStructHelper:
                        # TODO - This workaround is because this is shared by other pipeline calls that don't need generateStructHelper
                        if structTypeName != 'VkPipelineShaderStageCreateInfo':
                            # special case when dealing with isolated struct helper functions.
                            sTypeVuid = self.GetVuid(struct.name, "sType-sType")
                            usedLines.append(f'skip |= {context}ValidateStructType(loc, &info, {struct.sType}, false, kVUIDUndefined, {sTypeVuid});\n')
                    elif member.type in self.vk.handles:
                        if not member.optional:
                            usedLines.append(f'skip |= {context}ValidateRequiredHandle({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name});\n')
                    elif member.type in self.vk.flags and self.isFlagReserved(member.type):
                        vuid = self.GetVuid(callerName, f"{member.name}-zerobitmask")
                        usedLines.append(f'skip |= {context}ValidateReservedFlags({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name}, {vuid});\n')
                    elif member.type in self.vk.flags or member.type in self.vk.bitmasks:
                        if member.type in self.vk.flags:
                            flagBitsName = self.vk.flags[member.type].bitmaskName
                            flagsType = 'kOptionalFlags' if member.optional else 'kRequiredFlags'
                            invalidVuid = self.GetVuid(callerName, f"{member.name}-parameter")
                            zeroVuid = self.GetVuid(callerName, f"{member.name}-requiredbitmask")
                        elif member.type in self.vk.bitmasks:
                            flagBitsName = member.type
                            flagsType = 'kOptionalSingleBit' if member.optional else 'kRequiredSingleBit'
                            invalidVuid = self.GetVuid(callerName, f"{member.name}-parameter")
                            zeroVuid = invalidVuid
                        allFlagsName = 'All' + flagBitsName
                        zeroVuidArg = '' if member.optional else ', ' + zeroVuid
                        condition = [item for item in self.structMemberValidationConditions if (item['struct'] == structTypeName and item['field'] == flagBitsName)]
                        usedLines.append(f'skip |= {context}ValidateFlags({errorLoc}.dot(Field::{member.name}), vvl::FlagBitmask::{flagBitsName}, {allFlagsName}, {valuePrefix}{member.name}, {flagsType}, {invalidVuid}{zeroVuidArg});\n')
                    elif member.type == 'VkBool32':
                        usedLines.append(f'skip |= {context}ValidateBool32({errorLoc}.dot(Field::{member.name}), {valuePrefix}{member.name});\n')
                    elif member.type in self.vk.enums and member.type != 'VkStructureType':
                        vuid = self.GetVuid(callerName, f"{member.name}-parameter")
                        usedLines.append(f'skip |= {context}ValidateRangedEnum({errorLoc}.dot(Field::{member.name}), vvl::Enum::{member.type}, {valuePrefix}{member.name}, {vuid});\n')
                    # If this is a struct, see if it contains members that need to be checked
                    if member.type in self.validatedStructs:
                        memberNamePrefix = f'{valuePrefix}{member.name}.'
                        memberDisplayNamePrefix = f'{valueDisplayName}.'
                        usedLines.append(self.expandStructCode(member.type, funcName, errorLoc, memberNamePrefix, memberDisplayNamePrefix, [], context))
            # Append the parameter check to the function body for the current command
            if usedLines:
                # Apply special conditional checks
                if condition:
                    # Generate code to check for a specific condition before executing validation code
                    checkedExpr = []
                    checkedExpr.append(f'if ({condition[0]["condition"]}) {{\n')
                    for expr in usedLines:
                        checkedExpr.append(expr)
                    checkedExpr.append('}\n')
                    usedLines = [checkedExpr]

                lines += usedLines

        return lines

    # Joins strings in English fashion
    # TODO: move to some utility library
    def englishJoin(self, strings, conjunction: str):
        stringsList = list(strings)
        if len(stringsList) <= 1:
            return stringsList[0]
        else: # len > 1
            return f'{", ".join(stringsList[:-1])}, {conjunction} {stringsList[-1]}'


    # This logic was broken into its own function because we need to fill multiple functions with these structs
    def genStructBody(self, struct: Struct, nonPropFeature: bool, context: str):
        pNextCase = '\n'
        pNextCheck = ''

        pNextCase += f'        // Validation code for {struct.name} structure members\n'
        pNextCase += f'        case {struct.sType}: {{ // Covers VUID-{struct.name}-sType-sType\n'

        # TODO - https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9185
        if struct.name == 'VkPhysicalDeviceLayeredApiPropertiesListKHR':
            return ""

        expr = self.expandStructCode(struct.name, struct.name, 'pNext_loc', 'structure->', '', [], '')
        structValidationSource = self.ScrubStructCode(expr)
        if structValidationSource != '':
            # Only reasonable to validate content of structs if const as otherwise the date inside has not been writen to yet
            # https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/3122
            if struct.name in ['VkPhysicalDeviceLayeredApiPropertiesListKHR']:
                pNextCheck += 'if (true /* exception where we do not want to check for is_const_param */) {\n'
            else:
                pNextCheck += 'if (is_const_param) {\n'

            pNextCheck += f'[[maybe_unused]] const Location pNext_loc = loc.pNext(Struct::{struct.name});\n'

            structValidationSource = f'{struct.name} *structure = ({struct.name} *) header;\n{structValidationSource}'
            structValidationSource += '}\n'
        pNextCase += f'{pNextCheck}{structValidationSource}'
        pNextCase += '} break;\n'
        # Skip functions containing no validation
        if structValidationSource or pNextCheck != '':
            return pNextCase
        else:
            return f'\n        // No Validation code for {struct.name} structure members  -- Covers VUID-{struct.name}-sType-sType\n'

# Helper for iterating over a list where each element is possibly a single element or another 1-dimensional list
# Generates (setter, deleter, element) for each element where:
#  - element = the next element in the list
#  - setter(x) = a function that will set the entry in `lines` corresponding to `element` to `x`
#  - deleter() = a function that will delete the entry corresponding to `element` in `lines`
def multi_string_iter(lines):
    for i, ul in enumerate(lines):
        if not isinstance(ul, list):
            def setter(x): lines[i] = x
            def deleter(): del(lines[i])
            yield (setter, deleter, ul)
        else:
            for j, k in enumerate(lines[i]):
                def setter(x): lines[i][j] = x
                def deleter(): del(lines[i][j])
                yield (setter, deleter, k)