Home | History | Annotate | Download | only in loader
      1 #!/usr/bin/env python3
      2 #
      3 # Copyright (c) 2015-2016 The Khronos Group Inc.
      4 # Copyright (c) 2015-2016 Valve Corporation
      5 # Copyright (c) 2015-2016 LunarG, Inc.
      6 #
      7 # Permission is hereby granted, free of charge, to any person obtaining a copy
      8 # of this software and/or associated documentation files (the "Materials"), to
      9 # deal in the Materials without restriction, including without limitation the
     10 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     11 # sell copies of the Materials, and to permit persons to whom the Materials are
     12 # furnished to do so, subject to the following conditions:
     13 #
     14 # The above copyright notice(s) and this permission notice shall be included in
     15 # all copies or substantial portions of the Materials.
     16 #
     17 # THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     20 #
     21 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
     22 # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
     23 # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
     24 # USE OR OTHER DEALINGS IN THE MATERIALS.Copyright (C) 2015 Valve Corporation
     25 #
     26 # Author: Jon Ashburn <jon (at] lunarg.com>
     27 #
     28 
     29 import os, sys
     30 
     31 # add main repo directory so vulkan.py can be imported. This needs to be a complete path.
     32 ld_path = os.path.dirname(os.path.abspath(__file__))
     33 main_path = os.path.abspath(ld_path + "/../")
     34 sys.path.append(main_path)
     35 
     36 import vulkan
     37 
     38 def generate_get_proc_addr_check(name):
     39     return "    if (!%s || %s[0] != 'v' || %s[1] != 'k')\n" \
     40            "        return NULL;" % ((name,) * 3)
     41 
     42 class Subcommand(object):
     43     def __init__(self, argv):
     44         self.argv = argv
     45         self.headers = vulkan.headers
     46         self.protos = vulkan.protos
     47 
     48     def run(self):
     49         print(self.generate())
     50 
     51     def _requires_special_trampoline_code(self, name):
     52         # Dont be cute trying to use a general rule to programmatically populate this list
     53         # it just obsfucates what is going on!
     54         wsi_creates_dispatchable_object = ["CreateSwapchainKHR"]
     55         creates_dispatchable_object = ["CreateDevice", "GetDeviceQueue", "AllocateCommandBuffers"] + wsi_creates_dispatchable_object
     56         if name in creates_dispatchable_object:
     57             return True
     58         else:
     59            return False
     60 
     61     def _is_loader_non_trampoline_entrypoint(self, proto):
     62         if proto.name in ["GetDeviceProcAddr", "EnumeratePhysicalDevices", "EnumerateLayers", "DbgRegisterMsgCallback", "DbgUnregisterMsgCallback", "DbgSetGlobalOption", "DestroyInstance"]:
     63             return True
     64         return not self.is_dispatchable_object_first_param(proto)
     65 
     66 
     67     def is_dispatchable_object_first_param(self, proto):
     68         in_objs = proto.object_in_params()
     69         non_dispatch_objs = []
     70         param0 = proto.params[0]
     71         return (len(in_objs) > 0)  and (in_objs[0].ty == param0.ty) and (param0.ty not in non_dispatch_objs)
     72 
     73     def generate(self):
     74         copyright = self.generate_copyright()
     75         header = self.generate_header()
     76         body = self.generate_body()
     77         footer = self.generate_footer()
     78 
     79         contents = []
     80         if copyright:
     81             contents.append(copyright)
     82         if header:
     83             contents.append(header)
     84         if body:
     85             contents.append(body)
     86         if footer:
     87             contents.append(footer)
     88 
     89         return "\n\n".join(contents)
     90 
     91     def generate_copyright(self):
     92         return """/* THIS FILE IS GENERATED.  DO NOT EDIT. */
     93 
     94 /*
     95  * Copyright (c) 2015-2016 The Khronos Group Inc.
     96  * Copyright (c) 2015-2016 Valve Corporation
     97  * Copyright (c) 2015-2016 LunarG, Inc.
     98  *
     99  * Permission is hereby granted, free of charge, to any person obtaining a copy
    100  * of this software and/or associated documentation files (the "Materials"), to
    101  * deal in the Materials without restriction, including without limitation the
    102  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
    103  * sell copies of the Materials, and to permit persons to whom the Materials are
    104  * furnished to do so, subject to the following conditions:
    105  *
    106  * The above copyright notice(s) and this permission notice shall be included in
    107  * all copies or substantial portions of the Materials.
    108  *
    109  * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    110  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    111  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    112  *
    113  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
    114  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    115  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
    116  * USE OR OTHER DEALINGS IN THE MATERIALS.
    117  *
    118  * Author: Jon Ashburn <jon (at] lunarg.com>
    119  * Author: Chia-I Wu <olv (at] lunarg.com>
    120  * Author: Courtney Goeltzenleuchter <courtney (at] lunarg.com>
    121  */"""
    122 
    123     def generate_header(self):
    124         return "\n".join(["#include <" + h + ">" for h in self.headers])
    125 
    126     def generate_body(self):
    127         pass
    128 
    129     def generate_footer(self):
    130         pass
    131 
    132 class DevExtTrampolineSubcommand(Subcommand):
    133     def generate_header(self):
    134         lines = []
    135         lines.append("#include \"vk_loader_platform.h\"")
    136         lines.append("#include \"loader.h\"")
    137         lines.append("#if defined(__linux__)")
    138         lines.append("#pragma GCC optimize(3)  // force gcc to use tail-calls")
    139         lines.append("#endif")
    140         return "\n".join(lines)
    141 
    142     def generate_body(self):
    143         lines = []
    144         for i in range(250):
    145             lines.append('\nVKAPI_ATTR void VKAPI_CALL vkDevExt%s(VkDevice device)' % i)
    146             lines.append('{')
    147             lines.append('    const struct loader_dev_dispatch_table *disp;')
    148             lines.append('    disp = loader_get_dev_dispatch(device);')
    149             lines.append('    disp->ext_dispatch.DevExt[%s](device);' % i)
    150             lines.append('}')
    151         lines.append('')
    152         lines.append('void *loader_get_dev_ext_trampoline(uint32_t index)')
    153         lines.append('{')
    154         lines.append('    switch (index) {')
    155         for i in range(250):
    156             lines.append('        case %s:' % i)
    157             lines.append('            return vkDevExt%s;' % i)
    158         lines.append('    }')
    159         lines.append('    return NULL;')
    160         lines.append('}')
    161         return "\n".join(lines)
    162 
    163 class LoaderEntrypointsSubcommand(Subcommand):
    164     def generate_header(self):
    165         return "#include \"loader.h\""
    166 
    167     def _generate_object_setup(self, proto):
    168         method = "loader_init_dispatch"
    169         cond = "res == VK_SUCCESS"
    170         setup = []
    171 
    172         if not self._requires_special_trampoline_code(proto.name):
    173            return setup
    174 
    175         if "Get" in proto.name:
    176             method = "loader_set_dispatch"
    177 
    178         if proto.name == "GetSwapchainInfoKHR":
    179             ptype = proto.params[-3].name
    180             psize = proto.params[-2].name
    181             pdata = proto.params[-1].name
    182             cond = ("%s == VK_SWAP_CHAIN_INFO_TYPE_PERSISTENT_IMAGES_KHR && "
    183                     "%s && %s" % (ptype, pdata, cond))
    184             setup.append("VkSwapchainImageInfoKHR *info = %s;" % pdata)
    185             setup.append("size_t count = *%s / sizeof(*info), i;" % psize)
    186             setup.append("for (i = 0; i < count; i++) {")
    187             setup.append("    %s(info[i].image, disp);" % method)
    188             setup.append("    %s(info[i].memory, disp);" % method)
    189             setup.append("}")
    190         else:
    191             obj_params = proto.object_out_params()
    192             for param in obj_params:
    193                 setup.append("%s(*%s, disp);" % (method, param.name))
    194 
    195         if setup:
    196             joined = "\n        ".join(setup)
    197             setup = []
    198             setup.append("    if (%s) {" % cond)
    199             setup.append("        " + joined)
    200             setup.append("    }")
    201 
    202         return "\n".join(setup)
    203 
    204     def _generate_loader_dispatch_entrypoints(self, qual=""):
    205         if qual:
    206             qual += " "
    207 
    208         funcs = []
    209         for proto in self.protos:
    210             if self._is_loader_non_trampoline_entrypoint(proto):
    211                 continue
    212             func = []
    213 
    214             obj_setup = self._generate_object_setup(proto)
    215 
    216             func.append(qual + proto.c_func(prefix="vk", attr="VKAPI"))
    217             func.append("{")
    218 
    219             # declare local variables
    220             func.append("    const VkLayerDispatchTable *disp;")
    221             if proto.ret != 'void' and obj_setup:
    222                 func.append("    VkResult res;")
    223             func.append("")
    224 
    225             # get dispatch table
    226             func.append("    disp = loader_get_dispatch(%s);" %
    227                     proto.params[0].name)
    228             func.append("")
    229 
    230             # dispatch!
    231             dispatch = "disp->%s;" % proto.c_call()
    232             if proto.ret == 'void':
    233                 func.append("    " + dispatch)
    234             elif not obj_setup:
    235                 func.append("    return " + dispatch)
    236             else:
    237                 func.append("    res = " + dispatch)
    238                 func.append(obj_setup)
    239                 func.append("")
    240                 func.append("    return res;")
    241 
    242             func.append("}")
    243 
    244             funcs.append("\n".join(func))
    245 
    246         return "\n\n".join(funcs)
    247 
    248     def generate_body(self):
    249         body = [self._generate_loader_dispatch_entrypoints("LOADER_EXPORT")]
    250 
    251         return "\n\n".join(body)
    252 
    253 class DispatchTableOpsSubcommand(Subcommand):
    254     def run(self):
    255         if len(self.argv) != 1:
    256             print("DispatchTableOpsSubcommand: <prefix> unspecified")
    257             return
    258 
    259         self.prefix = self.argv[0]
    260         super().run()
    261 
    262     def generate_header(self):
    263         return "\n".join(["#include <vulkan/vulkan.h>",
    264                           "#include <vkLayer.h>",
    265                           "#include <string.h>",
    266                           "#include \"loader_platform.h\""])
    267 
    268     def _generate_init(self, type):
    269         stmts = []
    270         func = []
    271         if type == "device":
    272             for proto in self.protos:
    273                 if self.is_dispatchable_object_first_param(proto) or proto.name == "CreateInstance":
    274                     stmts.append("table->%s = (PFN_vk%s) gpa(gpu, \"vk%s\");" %
    275                         (proto.name, proto.name, proto.name))
    276                 else:
    277                     stmts.append("table->%s = vk%s; /* non-dispatchable */" %
    278                              (proto.name, proto.name))
    279             func.append("static inline void %s_init_device_dispatch_table(VkLayerDispatchTable *table,"
    280                 % self.prefix)
    281             func.append("%s                                              PFN_vkGetDeviceProcAddr gpa,"
    282                 % (" " * len(self.prefix)))
    283             func.append("%s                                              VkPhysicalDevice gpu)"
    284                 % (" " * len(self.prefix)))
    285         else:
    286             for proto in self.protos:
    287                 if proto.params[0].ty != "VkInstance" and proto.params[0].ty != "VkPhysicalDevice":
    288                     continue
    289                 stmts.append("table->%s = vk%s;" % (proto.name, proto.name))
    290             func.append("static inline void %s_init_instance_dispatch_table(VkLayerInstanceDispatchTable *table)"
    291                 % self.prefix)
    292         func.append("{")
    293         func.append("    %s" % "\n    ".join(stmts))
    294         func.append("}")
    295 
    296         return "\n".join(func)
    297 
    298     def _generate_lookup(self):
    299         lookups = []
    300         for proto in self.protos:
    301             if self.is_dispatchable_object_first_param(proto):
    302                 lookups.append("if (!strcmp(name, \"%s\"))" % (proto.name))
    303                 lookups.append("    return (void *) table->%s;"
    304                     % (proto.name))
    305 
    306         func = []
    307         func.append("static inline void *%s_lookup_dispatch_table(const VkLayerDispatchTable *table,"
    308                 % self.prefix)
    309         func.append("%s                                           const char *name)"
    310                 % (" " * len(self.prefix)))
    311         func.append("{")
    312         func.append(generate_get_proc_addr_check("name"))
    313         func.append("")
    314         func.append("    name += 2;")
    315         func.append("    %s" % "\n    ".join(lookups))
    316         func.append("")
    317         func.append("    return NULL;")
    318         func.append("}")
    319 
    320         return "\n".join(func)
    321 
    322     def generate_body(self):
    323         body = [self._generate_init("device"),
    324                 self._generate_lookup(),
    325                 self._generate_init("instance")]
    326 
    327         return "\n\n".join(body)
    328 
    329 class WinDefFileSubcommand(Subcommand):
    330     def run(self):
    331         library_exports = {
    332                 "all": [],
    333         }
    334 
    335         if len(self.argv) != 2 or self.argv[1] not in library_exports:
    336             print("WinDefFileSubcommand: <library-name> {%s}" %
    337                     "|".join(library_exports.keys()))
    338             return
    339 
    340         self.library = self.argv[0]
    341         self.exports = library_exports[self.argv[1]]
    342 
    343         super().run()
    344 
    345     def generate_copyright(self):
    346         return """; THIS FILE IS GENERATED.  DO NOT EDIT.
    347 
    348 ;;;; Begin Copyright Notice ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    349 ; Copyright (c) 2015-2016 The Khronos Group Inc.
    350 ; Copyright (c) 2015-2016 Valve Corporation
    351 ; Copyright (c) 2015-2016 LunarG, Inc.
    352 ;
    353 ; Permission is hereby granted, free of charge, to any person obtaining a copy
    354 ; of this software and/or associated documentation files (the "Materials"), to
    355 ; deal in the Materials without restriction, including without limitation the
    356 ; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
    357 ; sell copies of the Materials, and to permit persons to whom the Materials are
    358 ; furnished to do so, subject to the following conditions:
    359 ;
    360 ; The above copyright notice(s) and this permission notice shall be included in
    361 ; all copies or substantial portions of the Materials.
    362 ;
    363 ; THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    364 ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    365 ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    366 ;
    367 ; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
    368 ; DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    369 ; OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
    370 ; USE OR OTHER DEALINGS IN THE MATERIALS.
    371 ;
    372 ;
    373 ;  Author: Jon Ashburn <jon (at] lunarg.com>
    374 ;;;;  End Copyright Notice ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"""
    375 
    376     def generate_header(self):
    377         return "; The following is required on Windows, for exporting symbols from the DLL"
    378 
    379     def generate_body(self):
    380         body = []
    381 
    382         body.append("LIBRARY " + self.library)
    383         body.append("EXPORTS")
    384 
    385         for proto in self.protos:
    386             if self.exports and proto.name not in self.exports:
    387                 continue
    388 #           This was intended to reject WSI calls, but actually rejects ALL extensions
    389 #           TODO:  Make this WSI-extension specific
    390 #           if proto.name.endswith("KHR"):
    391 #               continue
    392             body.append("   vk" + proto.name)
    393 
    394         return "\n".join(body)
    395 
    396 class LoaderGetProcAddrSubcommand(Subcommand):
    397     def run(self):
    398         self.prefix = "vk"
    399 
    400         # we could get the list from argv if wanted
    401         self.intercepted = [proto.name for proto in self.protos]
    402 
    403         for proto in self.protos:
    404             if proto.name == "GetDeviceProcAddr":
    405                 self.gpa = proto
    406 
    407         super().run()
    408 
    409     def generate_header(self):
    410         return "\n".join(["#include <string.h>"])
    411 
    412     def generate_body(self):
    413         lookups = []
    414         for proto in self.protos:
    415             if proto.name not in self.intercepted:
    416                 lookups.append("/* no %s%s */" % (self.prefix, proto.name))
    417                 continue
    418 
    419             lookups.append("if (!strcmp(name, \"%s\"))" % proto.name)
    420             lookups.append("    return (%s) %s%s;" %
    421                     (self.gpa.ret, self.prefix, proto.name))
    422 
    423         special_lookups = []
    424         for proto in self.protos:
    425             if self._is_loader_non_trampoline_entrypoint(proto) or self._requires_special_trampoline_code(proto.name):
    426                 special_lookups.append("if (!strcmp(name, \"%s\"))" % proto.name)
    427                 special_lookups.append("    return (%s) %s%s;" %
    428                         (self.gpa.ret, self.prefix, proto.name))
    429             else:
    430                 continue
    431         body = []
    432         body.append("static inline %s globalGetProcAddr(const char *name)" %
    433                 self.gpa.ret)
    434         body.append("{")
    435         body.append(generate_get_proc_addr_check("name"))
    436         body.append("")
    437         body.append("    name += 2;")
    438         body.append("    %s" % "\n    ".join(lookups))
    439         body.append("")
    440         body.append("    return NULL;")
    441         body.append("}")
    442         body.append("")
    443         body.append("static inline void *loader_non_passthrough_gpa(const char *name)")
    444         body.append("{")
    445         body.append(generate_get_proc_addr_check("name"))
    446         body.append("")
    447         body.append("    name += 2;")
    448         body.append("    %s" % "\n    ".join(special_lookups))
    449         body.append("")
    450         body.append("    return NULL;")
    451         body.append("}")
    452 
    453         return "\n".join(body)
    454 
    455 def main():
    456 
    457     wsi = {
    458             "Win32",
    459             "Android",
    460             "Xcb",
    461             "Xlib",
    462             "Wayland",
    463             "Mir"
    464     }
    465 
    466     subcommands = {
    467             "dev-ext-trampoline": DevExtTrampolineSubcommand,
    468             "loader-entrypoints": LoaderEntrypointsSubcommand,
    469             "dispatch-table-ops": DispatchTableOpsSubcommand,
    470             "win-def-file": WinDefFileSubcommand,
    471             "loader-get-proc-addr": LoaderGetProcAddrSubcommand,
    472     }
    473 
    474     if len(sys.argv) < 3 or sys.argv[1] not in wsi or sys.argv[2] not in subcommands:
    475         print("Usage: %s <wsi> <subcommand> [options]" % sys.argv[0])
    476         print
    477         print("Available wsi (displayservers) are: %s" % " ".join(wsi))
    478         print("Available subcommands are: %s" % " ".join(subcommands))
    479         exit(1)
    480 
    481     subcmd = subcommands[sys.argv[2]](sys.argv[3:])
    482     subcmd.run()
    483 
    484 if __name__ == "__main__":
    485     main()
    486