1#!/usr/bin/env python
2
3# Copyright 2019 The Amber Authors. All rights reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17# Generates vk-wrappers.inc in the src/ directory.
18
19from __future__ import print_function
20
21import os.path
22import re
23import sys
24import xml.etree.ElementTree as ET
25from string import Template
26
27def read_inc(file):
28  methods = []
29  pattern = re.compile(r"AMBER_VK_FUNC\((\w+)\)")
30  with open(file, 'r') as f:
31    for line in f:
32      match = pattern.search(line)
33      if match == None:
34        raise Exception("FAILED TO MATCH PATTERN");
35
36      methods.append(match.group(1))
37  return methods
38
39
40def read_vk(file):
41  methods = {}
42  tree = ET.parse(file)
43  root = tree.getroot();
44  for command in root.iter("command"):
45    proto = command.find('proto')
46    if proto == None:
47      continue
48
49    return_type = proto.find('type').text
50    name = proto.find('name').text
51
52    param_list = []
53    for param in command.findall('param'):
54      param_val = "".join(param.itertext())
55      param_name = param.find('name').text
56      param_list.append({
57        'def': param_val,
58        'name': param_name
59      })
60
61    methods[name] = {
62      'return_type': return_type,
63      'name': name,
64      'params': param_list
65    }
66
67  return methods
68
69
70def gen_wrappers(methods, xml):
71  content = ""
72  for method in methods:
73    data = xml[method]
74    if data == None:
75      raise Exception("Failed to find {}".format(method))
76
77    param_vals = []
78    param_names = []
79    for param in data['params']:
80      param_vals.append(param['def'])
81      param_names.append(param['name'])
82
83    signature = ', '.join(str(x) for x in param_vals)
84    arguments = ', '.join(str(x) for x in param_names)
85    return_type = data['return_type']
86    return_variable = ''
87    call_prefix = ''
88    if return_type != 'void':
89      return_variable = 'ret'
90      call_prefix = return_type + ' ' + return_variable + ' = '
91
92    template = Template(R'''{
93  PFN_${method} ptr = reinterpret_cast<PFN_${method}>(getInstanceProcAddr(instance_, "${method}"));
94  if (!ptr) {
95    return Result("Vulkan: Unable to load ${method} pointer");
96  }
97  if (delegate && delegate->LogGraphicsCalls()) {
98    ptrs_.${method} = [ptr, delegate](${signature}) -> ${return_type} {
99      delegate->Log("${method}");
100      uint64_t timestamp_start = 0;
101      if (delegate->LogGraphicsCallsTime()) {
102        timestamp_start = delegate->GetTimestampNs();
103      }
104      ${call_prefix}ptr(${arguments});
105      if (delegate->LogGraphicsCallsTime()) {
106        uint64_t timestamp_end = delegate->GetTimestampNs();
107        uint64_t duration = timestamp_end - timestamp_start;
108        std::ostringstream out;
109        out << "time ";
110        // name of method on 40 characters
111        out << std::left << std::setw(40) << "${method}";
112        // duration in nanoseconds on 12 characters, right-aligned
113        out << std::right << std::setw(12) << duration;
114        out << " ns";
115        delegate->Log(out.str());
116      }
117      return ${return_variable};
118    };
119  } else {
120    ptrs_.${method} = [ptr](${signature}) -> ${return_type} {
121      ${call_prefix}ptr(${arguments});
122      return ${return_variable};
123    };
124  }
125}
126''')
127
128    content += template.substitute(method=method,
129                                   signature=signature,
130                                   arguments=arguments,
131                                   return_type=return_type,
132                                   return_variable=return_variable,
133                                   call_prefix=call_prefix)
134
135  return content
136
137
138def gen_headers(methods, xml):
139  content = ""
140  for method in methods:
141    data = xml[method]
142    if data == None:
143      raise Exception("Failed to find {}".format(method))
144
145    param_vals = []
146    param_names = []
147    for param in data['params']:
148      param_vals.append(param['def'])
149      param_names.append(param['name'])
150
151    content += "std::function<{}({})> {};\n".format(data['return_type'],
152        ', '.join(str(x) for x in param_vals), method)
153
154  return content
155
156
157def gen_direct(methods):
158  content = "";
159
160  template = Template(R'''
161if (!(ptrs_.${method} = reinterpret_cast<PFN_${method}>(getInstanceProcAddr(instance_, "${method}")))) {
162  return Result("Vulkan: Unable to load ${method} pointer");
163}
164''')
165
166  for method in methods:
167    content += template.substitute(method=method)
168
169  return content
170
171
172def gen_direct_headers(methods):
173  content = ""
174  for method in methods:
175    content += "PFN_{} {};\n".format(method, method);
176
177  return content
178
179
180def main():
181  if len(sys.argv) != 3:
182    print('usage: {} <outdir> <src_dir>'.format(
183      sys.argv[0]))
184    sys.exit(1)
185
186  outdir = sys.argv[1]
187  srcdir = sys.argv[2]
188
189  vulkan_versions = ("1-0", "1-1")
190
191  for vulkan_version in vulkan_versions:
192
193    vkfile = os.path.join(srcdir, 'third_party', 'vulkan-headers', 'registry', 'vk.xml')
194    incfile = os.path.join(srcdir, 'src', 'vulkan', 'vk-funcs-%s.inc' % vulkan_version)
195
196    data = read_inc(incfile)
197
198    wrapper_content = ''
199    header_content = ''
200    if os.path.isfile(vkfile):
201      vk_data = read_vk(vkfile)
202      wrapper_content = gen_wrappers(data, vk_data)
203      header_content = gen_headers(data, vk_data)
204    else:
205      wrapper_content = gen_direct(data)
206      header_content = gen_direct_headers(data)
207
208    outfile = os.path.join(outdir, 'vk-wrappers-%s.inc' % vulkan_version)
209    if os.path.isfile(outfile):
210      with open(outfile, 'r') as f:
211        if wrapper_content == f.read():
212          return
213    with open(outfile, 'w') as f:
214      f.write(wrapper_content)
215
216    hdrfile = os.path.join(outdir, 'vk-wrappers-%s.h' % vulkan_version)
217    if os.path.isfile(hdrfile):
218      with open(hdrfile, 'r') as f:
219        if header_content == f.read():
220          return
221    with open(hdrfile, 'w') as f:
222      f.write(header_content)
223
224
225if __name__ == '__main__':
226  main()
227