1# Copyright © 2020 Hoe Hao Cheng
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the "Software"),
5# to deal in the Software without restriction, including without limitation
6# the rights to use, copy, modify, merge, publish, distribute, sublicense,
7# and/or sell copies of the Software, and to permit persons to whom the
8# Software is furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice (including the next
11# paragraph) shall be included in all copies or substantial portions of the
12# Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21#
22# Authors:
23#    Hoe Hao Cheng <haochengho12907@gmail.com>
24#
25
26from mako.template import Template
27from mako.lookup import TemplateLookup
28from os import path
29from zink_extensions import Extension,ExtensionRegistry,Version
30import sys
31
32# constructor:
33#     Extension(name, alias="", required=False, properties=False, features=False, conditions=None, guard=False)
34# The attributes:
35#  - required: the generated code debug_prints "ZINK: {name} required!" and
36#              returns NULL if the extension is unavailable.
37#
38#  - properties: enable the detection of extension properties in a physical
39#                device in the generated code using vkGetPhysicalDeviceProperties2(),
40#                and store the returned properties struct inside
41#                `zink_device_info.{alias}_props`.
42#                Example: the properties for `VK_EXT_transform_feedback`, is stored in
43#                `VkPhysicalDeviceTransformFeedbackPropertiesEXT tf_props`.
44#
45#  - features: enable the getting extension features in a
46#              device. Similar to `properties`, this stores the features
47#              struct inside `zink_device_info.{alias}_feats`.
48#
49#  - conditions: criteria for enabling an extension. This is an array of strings,
50#                where each string is a condition, and all conditions have to be true
51#                for `zink_device_info.have_{name}` to be true.
52#
53#                The code generator will replace "$feats" and "$props" with the
54#                respective variables, e.g. "$feats.nullDescriptor" becomes
55#                "info->rb2_feats.nullDescriptor" in the final code for VK_EXT_robustness2.
56#
57#                When empty or None, the extension is enabled when the extensions
58#                given by vkEnumerateDeviceExtensionProperties() include the extension.
59#
60#  - guard: adds a #if defined(`extension_name`)/#endif guard around the code generated for this Extension.
61EXTENSIONS = [
62    Extension("VK_KHR_maintenance1",
63        required=True),
64    Extension("VK_KHR_maintenance2"),
65    Extension("VK_KHR_maintenance3"),
66    Extension("VK_KHR_external_memory"),
67    Extension("VK_KHR_external_memory_fd"),
68    Extension("VK_EXT_external_memory_dma_buf"),
69    Extension("VK_EXT_queue_family_foreign"),
70    Extension("VK_EXT_provoking_vertex",
71       alias="pv",
72       features=True,
73       properties=True,
74       conditions=["$feats.provokingVertexLast"]),
75    Extension("VK_EXT_shader_viewport_index_layer"),
76    Extension("VK_KHR_get_memory_requirements2"),
77    Extension("VK_EXT_post_depth_coverage"),
78    Extension("VK_EXT_shader_subgroup_ballot"),
79    Extension("VK_KHR_8bit_storage",
80              alias="storage_8bit",
81              features=True,
82              conditions=["$feats.storageBuffer8BitAccess"]),
83    Extension("VK_KHR_16bit_storage",
84              alias="storage_16bit",
85              features=True,
86              conditions=["$feats.storageBuffer16BitAccess"]),
87    Extension("VK_KHR_driver_properties",
88        alias="driver",
89        properties=True),
90    Extension("VK_EXT_memory_budget"),
91    Extension("VK_KHR_draw_indirect_count"),
92    Extension("VK_EXT_fragment_shader_interlock",
93       alias="interlock",
94       features=True,
95       conditions=["$feats.fragmentShaderSampleInterlock", "$feats.fragmentShaderPixelInterlock"]),
96    Extension("VK_EXT_sample_locations",
97       alias="sample_locations",
98       properties=True),
99    Extension("VK_EXT_conservative_rasterization",
100       alias="cons_raster",
101       properties=True,
102       conditions=["$props.fullyCoveredFragmentShaderInputVariable"]),
103    Extension("VK_KHR_shader_draw_parameters"),
104    Extension("VK_KHR_sampler_mirror_clamp_to_edge"),
105    Extension("VK_EXT_conditional_rendering",
106        alias="cond_render",
107        features=True,
108        conditions=["$feats.conditionalRendering"]),
109    Extension("VK_EXT_transform_feedback",
110        alias="tf",
111        properties=True,
112        features=True,
113        conditions=["$feats.transformFeedback"]),
114    Extension("VK_EXT_index_type_uint8",
115        alias="index_uint8",
116        features=True,
117        conditions=["$feats.indexTypeUint8"]),
118    Extension("VK_KHR_imageless_framebuffer",
119        alias="imgless",
120        features=True,
121        conditions=["$feats.imagelessFramebuffer"]),
122    Extension("VK_EXT_robustness2",
123        alias="rb2",
124        properties=True,
125        features=True,
126        conditions=["$feats.nullDescriptor"]),
127    Extension("VK_EXT_image_drm_format_modifier"),
128    Extension("VK_EXT_vertex_attribute_divisor",
129        alias="vdiv",
130        properties=True,
131        features=True,
132        conditions=["$feats.vertexAttributeInstanceRateDivisor"]),
133    Extension("VK_EXT_calibrated_timestamps"),
134    Extension("VK_KHR_shader_clock",
135       alias="shader_clock",
136       features=True,
137       conditions=["$feats.shaderSubgroupClock"]),
138    Extension("VK_EXT_sampler_filter_minmax",
139        alias="reduction",
140	properties=True),
141    Extension("VK_EXT_custom_border_color",
142        alias="border_color",
143        properties=True,
144        features=True,
145        conditions=["$feats.customBorderColors"]),
146    Extension("VK_EXT_blend_operation_advanced",
147        alias="blend",
148        properties=True,
149        # TODO: we can probably support non-premul here with some work?
150        conditions=["$props.advancedBlendNonPremultipliedSrcColor", "$props.advancedBlendNonPremultipliedDstColor"]),
151    Extension("VK_EXT_extended_dynamic_state",
152        alias="dynamic_state",
153        features=True,
154        conditions=["$feats.extendedDynamicState"]),
155    Extension("VK_EXT_extended_dynamic_state2",
156        alias="dynamic_state2",
157        features=True,
158        conditions=["$feats.extendedDynamicState2"]),
159    Extension("VK_EXT_pipeline_creation_cache_control",
160        alias="pipeline_cache_control",
161        features=True,
162        conditions=["$feats.pipelineCreationCacheControl"]),
163    Extension("VK_EXT_shader_stencil_export",
164        alias="stencil_export"),
165    Extension("VK_EXTX_portability_subset",
166        alias="portability_subset_extx",
167        nonstandard=True,
168        properties=True,
169        features=True,
170        guard=True),
171    Extension("VK_KHR_timeline_semaphore", alias="timeline", features=True),
172    Extension("VK_EXT_4444_formats",
173        alias="format_4444",
174        features=True),
175    Extension("VK_EXT_scalar_block_layout",
176        alias="scalar_block_layout",
177        features=True,
178        conditions=["$feats.scalarBlockLayout"]),
179    Extension("VK_KHR_swapchain"),
180    Extension("VK_KHR_shader_float16_int8",
181              alias="shader_float16_int8",
182              features=True),
183    Extension("VK_EXT_multi_draw",
184              alias="multidraw",
185	      features=True,
186	      properties=True,
187	      conditions=["$feats.multiDraw"]),
188    Extension("VK_KHR_push_descriptor",
189        alias="push",
190        properties=True),
191    Extension("VK_KHR_descriptor_update_template",
192        alias="template"),
193    Extension("VK_EXT_line_rasterization",
194        alias="line_rast",
195        properties=True,
196        features=True),
197    Extension("VK_EXT_vertex_input_dynamic_state",
198        alias="vertex_input",
199	features=True,
200	conditions=["$feats.vertexInputDynamicState"]),
201    Extension("VK_EXT_primitive_topology_list_restart",
202        alias="list_restart",
203	features=True,
204	conditions=["$feats.primitiveTopologyListRestart"]),
205    Extension("VK_KHR_dedicated_allocation",
206        alias="dedicated"),
207    Extension("VK_EXT_descriptor_indexing",
208        alias="desc_indexing",
209        features=True,
210        properties=True,
211        conditions=["$feats.descriptorBindingPartiallyBound"]),
212]
213
214# constructor: Versions(device_version(major, minor, patch), struct_version(major, minor))
215# The attributes:
216#  - device_version: Vulkan version, as tuple, to use with
217#                    VK_MAKE_VERSION(version_major, version_minor, version_patch)
218#
219#  - struct_version: Vulkan version, as tuple, to use with structures and macros
220VERSIONS = [
221    Version((1,1,0), (1,1)),
222    Version((1,2,0), (1,2)),
223]
224
225# There exists some inconsistencies regarding the enum constants, fix them.
226# This is basically generated_code.replace(key, value).
227REPLACEMENTS = {
228    "ROBUSTNESS2": "ROBUSTNESS_2",
229    "PROPERTIES_PROPERTIES": "PROPERTIES",
230    "EXTENDED_DYNAMIC_STATE2": "EXTENDED_DYNAMIC_STATE_2",
231}
232
233
234# This template provides helper functions for the other templates.
235# Right now, the following functions are defined:
236# - guard(ext) : surrounds the body with an if-def guard according to
237#                `ext.extension_name()` if `ext.guard` is True.
238include_template = """
239<%def name="guard_(ext, body)">
240%if ext.guard:
241#ifdef ${ext.extension_name()}
242%endif
243   ${capture(body)|trim}
244%if ext.guard:
245#endif
246%endif
247</%def>
248
249## This ugliness is here to prevent mako from adding tons of excessive whitespace
250<%def name="guard(ext)">${capture(guard_, ext, body=caller.body).strip('\\r\\n')}</%def>
251"""
252
253header_code = """
254<%namespace name="helpers" file="helpers"/>
255
256#ifndef ZINK_DEVICE_INFO_H
257#define ZINK_DEVICE_INFO_H
258
259#include "util/u_memory.h"
260
261#include <vulkan/vulkan.h>
262
263struct zink_screen;
264
265struct zink_device_info {
266   uint32_t device_version;
267
268%for ext in extensions:
269<%helpers:guard ext="${ext}">
270   bool have_${ext.name_with_vendor()};
271</%helpers:guard>
272%endfor
273%for version in versions:
274   bool have_vulkan${version.struct()};
275%endfor
276
277   VkPhysicalDeviceFeatures2 feats;
278%for version in versions:
279   VkPhysicalDeviceVulkan${version.struct()}Features feats${version.struct()};
280%endfor
281
282   VkPhysicalDeviceProperties props;
283%for version in versions:
284   VkPhysicalDeviceVulkan${version.struct()}Properties props${version.struct()};
285%endfor
286
287   VkPhysicalDeviceMemoryProperties mem_props;
288
289%for ext in extensions:
290<%helpers:guard ext="${ext}">
291%if ext.has_features:
292   ${ext.physical_device_struct("Features")} ${ext.field("feats")};
293%endif
294%if ext.has_properties:
295   ${ext.physical_device_struct("Properties")} ${ext.field("props")};
296%endif
297</%helpers:guard>
298%endfor
299
300    const char *extensions[${len(extensions)}];
301    uint32_t num_extensions;
302};
303
304bool
305zink_get_physical_device_info(struct zink_screen *screen);
306
307void
308zink_verify_device_extensions(struct zink_screen *screen);
309
310/* stub functions that get inserted into the dispatch table if they are not
311 * properly loaded.
312 */
313%for ext in extensions:
314%if registry.in_registry(ext.name):
315%for cmd in registry.get_registry_entry(ext.name).device_commands:
316void zink_stub_${cmd.lstrip("vk")}(void);
317%endfor
318%endif
319%endfor
320
321#endif
322"""
323
324
325impl_code = """
326<%namespace name="helpers" file="helpers"/>
327
328#include "zink_device_info.h"
329#include "zink_screen.h"
330
331bool
332zink_get_physical_device_info(struct zink_screen *screen)
333{
334   struct zink_device_info *info = &screen->info;
335%for ext in extensions:
336<%helpers:guard ext="${ext}">
337   bool support_${ext.name_with_vendor()} = false;
338</%helpers:guard>
339%endfor
340   uint32_t num_extensions = 0;
341
342   // get device memory properties
343   vkGetPhysicalDeviceMemoryProperties(screen->pdev, &info->mem_props);
344
345   // enumerate device supported extensions
346   if (vkEnumerateDeviceExtensionProperties(screen->pdev, NULL, &num_extensions, NULL) == VK_SUCCESS) {
347      if (num_extensions > 0) {
348         VkExtensionProperties *extensions = MALLOC(sizeof(VkExtensionProperties) * num_extensions);
349         if (!extensions) goto fail;
350         vkEnumerateDeviceExtensionProperties(screen->pdev, NULL, &num_extensions, extensions);
351
352         for (uint32_t i = 0; i < num_extensions; ++i) {
353         %for ext in extensions:
354         <%helpers:guard ext="${ext}">
355            if (!strcmp(extensions[i].extensionName, "${ext.name}")) {
356         %if not (ext.has_features or ext.has_properties):
357               info->have_${ext.name_with_vendor()} = true;
358         %else:
359               support_${ext.name_with_vendor()} = true;
360         %endif
361            }
362         </%helpers:guard>
363         %endfor
364         }
365
366         FREE(extensions);
367      }
368   }
369
370   // get device features
371   if (screen->vk.GetPhysicalDeviceFeatures2) {
372      // check for device extension features
373      info->feats.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
374
375%for version in versions:
376%if version.device_version < (1,2,0):
377      if (VK_MAKE_VERSION(1,2,0) <= screen->vk_version) {
378         /* VkPhysicalDeviceVulkan11Features was added in 1.2, not 1.1 as one would think */
379%else:
380      if (${version.version()} <= screen->vk_version) {
381%endif
382         info->feats${version.struct()}.sType = ${version.stype("FEATURES")};
383         info->feats${version.struct()}.pNext = info->feats.pNext;
384         info->feats.pNext = &info->feats${version.struct()};
385         info->have_vulkan${version.struct()} = true;
386      }
387%endfor
388
389%for ext in extensions:
390%if ext.has_features:
391<%helpers:guard ext="${ext}">
392      if (support_${ext.name_with_vendor()}) {
393         info->${ext.field("feats")}.sType = ${ext.stype("FEATURES")};
394         info->${ext.field("feats")}.pNext = info->feats.pNext;
395         info->feats.pNext = &info->${ext.field("feats")};
396      }
397</%helpers:guard>
398%endif
399%endfor
400
401      screen->vk.GetPhysicalDeviceFeatures2(screen->pdev, &info->feats);
402   } else {
403      vkGetPhysicalDeviceFeatures(screen->pdev, &info->feats.features);
404   }
405
406   // check for device properties
407   if (screen->vk.GetPhysicalDeviceProperties2) {
408      VkPhysicalDeviceProperties2 props = {0};
409      props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
410
411%for version in versions:
412%if version.device_version < (1,2,0):
413      if (VK_MAKE_VERSION(1,2,0) <= screen->vk_version) {
414         /* VkPhysicalDeviceVulkan11Properties was added in 1.2, not 1.1 as one would think */
415%else:
416      if (${version.version()} <= screen->vk_version) {
417%endif
418         info->props${version.struct()}.sType = ${version.stype("PROPERTIES")};
419         info->props${version.struct()}.pNext = props.pNext;
420         props.pNext = &info->props${version.struct()};
421      }
422%endfor
423
424%for ext in extensions:
425%if ext.has_properties:
426<%helpers:guard ext="${ext}">
427      if (support_${ext.name_with_vendor()}) {
428         info->${ext.field("props")}.sType = ${ext.stype("PROPERTIES")};
429         info->${ext.field("props")}.pNext = props.pNext;
430         props.pNext = &info->${ext.field("props")};
431      }
432</%helpers:guard>
433%endif
434%endfor
435
436      // note: setting up local VkPhysicalDeviceProperties2.
437      screen->vk.GetPhysicalDeviceProperties2(screen->pdev, &props);
438   }
439
440   // enable the extensions if they match the conditions given by ext.enable_conds
441   if (screen->vk.GetPhysicalDeviceProperties2) {
442        %for ext in extensions:
443<%helpers:guard ext="${ext}">
444<%
445    conditions = ""
446    if ext.enable_conds:
447        for cond in ext.enable_conds:
448            cond = cond.replace("$feats", "info->" + ext.field("feats"))
449            cond = cond.replace("$props", "info->" + ext.field("props"))
450            conditions += "&& (" + cond + ")\\n"
451    conditions = conditions.strip()
452%>\
453      info->have_${ext.name_with_vendor()} |= support_${ext.name_with_vendor()}
454         ${conditions};
455</%helpers:guard>
456        %endfor
457   }
458
459   // generate extension list
460   num_extensions = 0;
461
462%for ext in extensions:
463<%helpers:guard ext="${ext}">
464   if (info->have_${ext.name_with_vendor()}) {
465       info->extensions[num_extensions++] = "${ext.name}";
466%if ext.is_required:
467   } else {
468       debug_printf("ZINK: ${ext.name} required!\\n");
469       goto fail;
470%endif
471   }
472</%helpers:guard>
473%endfor
474
475   info->num_extensions = num_extensions;
476
477   return true;
478
479fail:
480   return false;
481}
482
483void
484zink_verify_device_extensions(struct zink_screen *screen)
485{
486%for ext in extensions:
487%if registry.in_registry(ext.name):
488   if (screen->info.have_${ext.name_with_vendor()}) {
489%for cmd in registry.get_registry_entry(ext.name).device_commands:
490      if (!screen->vk.${cmd.lstrip("vk")}) {
491#ifndef NDEBUG
492         screen->vk.${cmd.lstrip("vk")} = (PFN_${cmd})zink_stub_${cmd.lstrip("vk")};
493#else
494         screen->vk.${cmd.lstrip("vk")} = (PFN_${cmd})zink_stub_function_not_loaded;
495#endif
496      }
497%endfor
498   }
499%endif
500%endfor
501}
502
503#ifndef NDEBUG
504/* generated stub functions */
505## remember the stub functions that are already generated
506<% generated_funcs = set() %>
507
508%for ext in extensions:
509%if registry.in_registry(ext.name):
510%for cmd in registry.get_registry_entry(ext.name).device_commands:
511##
512## some functions are added by multiple extensions, which creates duplication
513## and thus redefinition of stubs (eg. vkCmdPushDescriptorSetWithTemplateKHR)
514##
515%if cmd in generated_funcs:
516   <% continue %>
517%else:
518   <% generated_funcs.add(cmd) %>
519%endif
520void
521zink_stub_${cmd.lstrip("vk")}()
522{
523   mesa_loge("ZINK: ${cmd} is not loaded properly!");
524   abort();
525}
526%endfor
527%endif
528%endfor
529#endif
530"""
531
532
533def replace_code(code: str, replacement: dict):
534    for (k, v) in replacement.items():
535        code = code.replace(k, v)
536
537    return code
538
539
540if __name__ == "__main__":
541    try:
542        header_path = sys.argv[1]
543        impl_path = sys.argv[2]
544        vkxml_path = sys.argv[3]
545
546        header_path = path.abspath(header_path)
547        impl_path = path.abspath(impl_path)
548        vkxml_path = path.abspath(vkxml_path)
549    except:
550        print("usage: %s <path to .h> <path to .c> <path to vk.xml>" % sys.argv[0])
551        exit(1)
552
553    registry = ExtensionRegistry(vkxml_path)
554
555    extensions = EXTENSIONS
556    versions = VERSIONS
557    replacement = REPLACEMENTS
558
559    # Perform extension validation and set core_since for the extension if available
560    error_count = 0
561    for ext in extensions:
562        if not registry.in_registry(ext.name):
563            # disable validation for nonstandard extensions
564            if ext.is_nonstandard:
565                continue
566
567            error_count += 1
568            print("The extension {} is not registered in vk.xml - a typo?".format(ext.name))
569            continue
570
571        entry = registry.get_registry_entry(ext.name)
572
573        if entry.ext_type != "device":
574            error_count += 1
575            print("The extension {} is {} extension - expected a device extension.".format(ext.name, entry.ext_type))
576            continue
577
578        if ext.has_features:
579            if not (entry.features_struct and ext.physical_device_struct("Features") == entry.features_struct):
580                error_count += 1
581                print("The extension {} does not provide a features struct.".format(ext.name))
582
583        if ext.has_properties:
584            if not (entry.properties_struct and ext.physical_device_struct("Properties") == entry.properties_struct):
585                error_count += 1
586                print("The extension {} does not provide a properties struct.".format(ext.name))
587                print(entry.properties_struct, ext.physical_device_struct("Properties"))
588
589        if entry.promoted_in:
590            ext.core_since = Version((*entry.promoted_in, 0))
591
592    if error_count > 0:
593        print("zink_device_info.py: Found {} error(s) in total. Quitting.".format(error_count))
594        exit(1)
595
596    lookup = TemplateLookup()
597    lookup.put_string("helpers", include_template)
598
599    with open(header_path, "w") as header_file:
600        header = Template(header_code, lookup=lookup).render(extensions=extensions, versions=versions, registry=registry).strip()
601        header = replace_code(header, replacement)
602        print(header, file=header_file)
603
604    with open(impl_path, "w") as impl_file:
605        impl = Template(impl_code, lookup=lookup).render(extensions=extensions, versions=versions, registry=registry).strip()
606        impl = replace_code(impl, replacement)
607        print(impl, file=impl_file)
608