1#!/usr/bin/python3
2# Copyright 2017 The ANGLE Project Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5#
6# generate_gl_dispatch_table.py:
7#  Generation script for OpenGL bindings with ANGLE.
8#  NOTE: don't run this script directly. Run scripts/run_code_generation.py.
9
10import sys
11import os
12import re
13import xml.etree.ElementTree as etree
14
15# Set the CWD to the script directory.
16os.chdir(os.path.dirname(os.path.abspath(sys.argv[0])))
17
18sys.path.append('..')
19import angle_format
20
21def safe_append(the_dict, key, element):
22    if key not in the_dict:
23        the_dict[key] = []
24    the_dict[key].append(element)
25
26
27# Template for the header declaration of the dispatch table.
28dispatch_table_header_template = """// GENERATED FILE - DO NOT EDIT.
29// Generated by {script_name} using data from {data_source_name} and gl.xml.
30//
31// Copyright 2017 The ANGLE Project Authors. All rights reserved.
32// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
34//
35// {file_name}:
36//   Defines the native binding interface for ANGLE's OpenGL back-end.
37
38#ifndef LIBGLESV2_RENDERER_GL_DISPATCH_TABLE_GL_AUTOGEN_H_
39#define LIBGLESV2_RENDERER_GL_DISPATCH_TABLE_GL_AUTOGEN_H_
40
41#include "common/angleutils.h"
42#include "libANGLE/renderer/gl/functionsgl_typedefs.h"
43
44#include <set>
45
46namespace gl
47{{
48struct Version;
49}}  // namespace gl
50
51namespace rx
52{{
53class DispatchTableGL : angle::NonCopyable
54{{
55  public:
56  // clang-format off
57{table_data}
58  // clang-format on
59
60  DispatchTableGL();
61  virtual ~DispatchTableGL() = default;
62
63  protected:
64    virtual void *loadProcAddress(const std::string &function) const = 0;
65
66    void initProcsDesktopGL(const gl::Version &version, const std::set<std::string> &extensions);
67    void initProcsGLES(const gl::Version &version, const std::set<std::string> &extensions);
68    void initProcsSharedExtensions(const std::set<std::string> &extensions);
69
70#if defined(ANGLE_ENABLE_OPENGL_NULL)
71    void initProcsDesktopGLNULL(const gl::Version &version, const std::set<std::string> &extensions);
72    void initProcsGLESNULL(const gl::Version &version, const std::set<std::string> &extensions);
73    void initProcsSharedExtensionsNULL(const std::set<std::string> &extensions);
74#endif  // defined(ANGLE_ENABLE_OPENGL_NULL)
75}};
76
77}}  // namespace rx
78
79#endif  // LIBGLESV2_RENDERER_GL_DISPATCH_TABLE_GL_AUTOGEN_H_
80"""
81
82
83def first_lower(str):
84    return str[:1].lower() + str[1:]
85
86
87def format_ep_decl(entry_point):
88    return "    PFNGL" + entry_point.upper() + "PROC " + first_lower(entry_point) + " = nullptr;"
89
90
91# Template for the initialization file of the dispatch table.
92dispatch_table_source_template = """// GENERATED FILE - DO NOT EDIT.
93// Generated by {script_name} using data from {data_source_name} and gl.xml.
94//
95// Copyright 2017 The ANGLE Project Authors. All rights reserved.
96// Use of this source code is governed by a BSD-style license that can be
97// found in the LICENSE file.
98//
99// {file_name}:
100//   Initialize the native bindings for ANGLE's OpenGL back-end.
101
102#include "libANGLE/renderer/gl/DispatchTableGL_autogen.h"
103
104#include "libANGLE/Version.h"
105#include "libANGLE/renderer/gl/FunctionsGL.h"
106
107#if defined(ANGLE_ENABLE_OPENGL_NULL)
108#include "libANGLE/renderer/gl/null_functions.h"
109#endif  // defined(ANGLE_ENABLE_OPENGL_NULL)
110
111#define ASSIGN(NAME, FP) do {{ FP = reinterpret_cast<decltype(FP)>(loadProcAddress(NAME)); }} while (0)
112
113namespace rx
114{{
115DispatchTableGL::DispatchTableGL() = default;
116
117void DispatchTableGL::initProcsDesktopGL(const gl::Version &version, const std::set<std::string> &extensions)
118{{
119#if defined(ANGLE_ENABLE_OPENGL_DESKTOP)
120{gl_extensions_data}
121
122{gl_data}
123#endif  // defined(ANGLE_ENABLE_OPENGL_DESKTOP)
124}}
125
126void DispatchTableGL::initProcsGLES(const gl::Version &version, const std::set<std::string> &extensions)
127{{
128{gles2_extensions_data}
129
130{gles2_data}
131}}
132
133void DispatchTableGL::initProcsSharedExtensions(const std::set<std::string> &extensions)
134{{
135{both_extensions_data}
136}}
137
138#if defined(ANGLE_ENABLE_OPENGL_NULL)
139void DispatchTableGL::initProcsDesktopGLNULL(const gl::Version &version, const std::set<std::string> &extensions)
140{{
141#if defined(ANGLE_ENABLE_OPENGL_DESKTOP)
142{gl_null_extensions_data}
143
144{gl_null_data}
145#endif  // defined(ANGLE_ENABLE_OPENGL_DESKTOP)
146}}
147
148void DispatchTableGL::initProcsGLESNULL(const gl::Version &version, const std::set<std::string> &extensions)
149{{
150{gles2_null_extensions_data}
151
152{gles2_null_data}
153}}
154
155void DispatchTableGL::initProcsSharedExtensionsNULL(const std::set<std::string> &extensions)
156{{
157{both_null_extensions_data}
158}}
159#endif  // defined(ANGLE_ENABLE_OPENGL_NULL)
160
161}}  // namespace rx
162"""
163
164
165def format_assign_ep(entry_point, ep):
166    return '        ASSIGN("' + ep + '", ' + first_lower(entry_point[2:]) + ');'
167
168
169def format_requirements_lines(required, entry_points):
170    major, minor = required
171    lines = ['    if (version >= gl::Version(' + major + ', ' + minor + '))', '    {']
172    lines += [format_assign_ep(entry_point, entry_point) for entry_point in sorted(entry_points)]
173    lines += ['    }']
174    return '\n'.join(lines)
175
176
177def format_extension_requirements_lines(extension, entry_points, api):
178    lines = ['    if (extensions.count("' + extension + '") != 0)', '    {']
179    lines += [format_assign_ep(entry_point, ep) for entry_point, ep in sorted(entry_points)]
180    lines += ['    }']
181    return '\n'.join(lines)
182
183
184def assign_null_line(line):
185    m = re.match(r'        ASSIGN\("gl.*", (.+)\);', line)
186    if m:
187        name = m.group(1)
188        return '        ' + name + ' = &gl' + name[0].upper() + name[1:] + 'NULL;'
189    else:
190        return line
191
192
193def assign_null(entry):
194    return '\n'.join([assign_null_line(line) for line in entry.split('\n')])
195
196
197def nullify(data):
198    return [assign_null(entry) for entry in data]
199
200
201def format_param(param):
202    return "".join(param.itertext())
203
204
205null_functions_header_template = """// GENERATED FILE - DO NOT EDIT.
206// Generated by {script_name} using data from {data_source_name} and gl.xml.
207//
208// Copyright 2017 The ANGLE Project Authors. All rights reserved.
209// Use of this source code is governed by a BSD-style license that can be
210// found in the LICENSE file.
211//
212// {file_name}:
213//   Declares the NULL/Stub bindings for the OpenGL back-end.
214
215#ifndef LIBGLESV2_RENDERER_GL_NULL_GL_FUNCTIONS_AUTOGEN_H_
216#define LIBGLESV2_RENDERER_GL_NULL_GL_FUNCTIONS_AUTOGEN_H_
217
218#include "libANGLE/renderer/gl/functionsgl_typedefs.h"
219
220namespace rx
221{{
222{table_data}
223}}  // namespace rx
224
225#endif  // LIBGLESV2_RENDERER_GL_NULL_GL_FUNCTIONS_AUTOGEN_H_
226"""
227
228null_functions_source_template = """// GENERATED FILE - DO NOT EDIT.
229// Generated by {script_name} using data from {data_source_name} and gl.xml.
230//
231// Copyright 2017 The ANGLE Project Authors. All rights reserved.
232// Use of this source code is governed by a BSD-style license that can be
233// found in the LICENSE file.
234//
235// {file_name}:
236//   Defines the NULL/Stub bindings for the OpenGL back-end.
237
238#include "libANGLE/renderer/gl/null_functions.h"
239
240namespace rx
241{{
242{table_data}
243}}  // namespace rx
244"""
245
246
247def main():
248
249    # auto_script parameters.
250    if len(sys.argv) > 1:
251        inputs = [
252            '../../../../scripts/gl.xml',
253            '../angle_format.py',
254            'gl_bindings_data.json',
255        ]
256        outputs = [
257            'DispatchTableGL_autogen.cpp',
258            'DispatchTableGL_autogen.h',
259            'null_functions.cpp',
260            'null_functions.h',
261        ]
262
263        if sys.argv[1] == 'inputs':
264            print(','.join(inputs))
265        elif sys.argv[1] == 'outputs':
266            print(','.join(outputs))
267        else:
268            print('Invalid script parameters')
269            return 1
270        return 0
271
272    gl_xml_path = os.path.join('..', '..', '..', '..', 'scripts', 'gl.xml')
273    dispatch_header_path = 'DispatchTableGL_autogen.h'
274    dispatch_source_path = 'DispatchTableGL_autogen.cpp'
275    null_functions_header_path = 'null_functions.h'
276    null_functions_source_path = 'null_functions.cpp'
277
278    # Load the JSON and XML data.
279    data_source_name = 'gl_bindings_data.json'
280    json_data = angle_format.load_json(data_source_name)
281    xml_root = etree.parse(gl_xml_path).getroot()
282
283    api_feature_info = {}
284
285    core_removed_eps = []
286    for core_removed_ep in xml_root.findall('feature/remove'):
287        assert (core_removed_ep.attrib['profile'] == 'core')
288        for command in core_removed_ep.findall('./command'):
289            core_removed_eps.append(command.attrib['name'])
290
291    for feature in xml_root.findall('feature'):
292        api = feature.attrib['api']
293        name = feature.attrib['name']
294        number = feature.attrib['number']
295
296        # OpenGL ES 3.x versions are listed as api 'gles2'
297        if api != 'gl' and api != 'gles2':
298            continue
299
300        for command in feature.findall('./require/command'):
301            command_name = command.attrib['name']
302            safe_append(api_feature_info, command_name, (api, name, number))
303
304    gl_extension_commands = {}
305    gles2_extension_commands = {}
306    both_extension_commands = {}
307
308    for extension in xml_root.findall('extensions/extension'):
309        extension_name = extension.attrib['name']
310        support = extension.attrib['supported'].split('|')
311        for command in extension.findall('./require/command'):
312            command_name = command.attrib['name']
313            if 'gl' in support and 'gles2' in support:
314                # Special case for KHR extensions, since in GLES they are suffixed.
315                if '_KHR_' in extension_name and not command_name.endswith('KHR'):
316                    safe_append(gl_extension_commands, command_name, extension_name)
317                    safe_append(gles2_extension_commands, command_name, extension_name)
318                else:
319                    safe_append(both_extension_commands, command_name, extension_name)
320            elif 'gl' in support:
321                safe_append(gl_extension_commands, command_name, extension_name)
322            elif 'gles2' in support:
323                safe_append(gles2_extension_commands, command_name, extension_name)
324
325    gl_requirements = {}
326    gles2_requirements = {}
327    gl_extension_requirements = {}
328    gles2_extension_requirements = {}
329    both_extension_requirements = {}
330
331    # Used later in the NULL bindings.
332    all_entry_points = []
333
334    for comment, entry_points in sorted(json_data.items()):
335        for entry_point_no_prefix in entry_points:
336            entry_point = "gl" + entry_point_no_prefix
337
338            all_entry_points.append(entry_point)
339
340            gl_required = None
341            gles2_required = None
342
343            if entry_point in api_feature_info:
344                for api, name, number in api_feature_info[entry_point]:
345                    major, minor = number.split(".")
346                    reqs = (major, minor)
347                    if api == 'gl':
348                        if not gl_required:
349                            gl_required = reqs
350                        elif entry_point in core_removed_eps:
351                            print('Upgrade ' + entry_point + ' to ' + str(reqs) + ' instead of ' +
352                                  str(gl_required))
353                            gl_required = reqs
354                        else:
355                            print('Keep ' + entry_point + ' at ' + str(gl_required) +
356                                  ' instead of ' + str(reqs))
357                    elif api == 'gles2':
358                        if not gles2_required:
359                            gles2_required = reqs
360                        else:
361                            print("Duplicate for " + entry_point + ": " + str(reqs) + " and " +
362                                  str(gles2_required))
363                    else:
364                        raise Exception('Bad api type: ' + api)
365
366            if gl_required:
367                safe_append(gl_requirements, gl_required, entry_point)
368
369            if gles2_required:
370                safe_append(gles2_requirements, gles2_required, entry_point)
371
372            # Special case for finding extensions that overlap with core functions.
373
374            extension = False
375
376            for ep in [entry_point, entry_point + "EXT", entry_point + "ARB", entry_point + "OES"]:
377                if ep in both_extension_commands:
378                    extension = True
379                    for extension in both_extension_commands[ep]:
380                        safe_append(both_extension_requirements, extension, (entry_point, ep))
381
382                else:
383                    if ep in gl_extension_commands:
384                        extension = True
385                        for extension in gl_extension_commands[ep]:
386                            safe_append(gl_extension_requirements, extension, (entry_point, ep))
387
388                    if ep in gles2_extension_commands:
389                        extension = True
390                        for extension in gles2_extension_commands[ep]:
391                            full_ep = ep
392                            if '_KHR_' in extension:
393                                full_ep += 'KHR'
394                            safe_append(gles2_extension_requirements, extension,
395                                        (entry_point, full_ep))
396
397            if not (gl_required or gles2_required or extension):
398                raise Exception('Entry point ' + entry_point + ' not found in the xml.')
399
400    table_data = []
401    for comment, entry_points in sorted(json_data.items()):
402        formatted = ["    // " + comment]
403        formatted += [format_ep_decl(entry_point) for entry_point in sorted(entry_points)]
404
405        table_data.append("\n".join(formatted))
406
407    dispatch_table_header = dispatch_table_header_template.format(
408        script_name=os.path.basename(sys.argv[0]),
409        data_source_name=data_source_name,
410        file_name=dispatch_header_path,
411        table_data="\n\n".join(table_data))
412
413    with open(dispatch_header_path, "w") as out:
414        out.write(dispatch_table_header)
415
416    gl_data = []
417    for gl_required, entry_points in sorted(gl_requirements.items()):
418        gl_data.append(format_requirements_lines(gl_required, entry_points))
419
420    gl_extensions_data = []
421    for extension, entry_points in sorted(gl_extension_requirements.items()):
422        gl_extensions_data.append(
423            format_extension_requirements_lines(extension, entry_points, "gl"))
424
425    gles2_data = []
426    for gles2_required, entry_points in sorted(gles2_requirements.items()):
427        gles2_data.append(format_requirements_lines(gles2_required, entry_points))
428
429    gles2_extensions_data = []
430    for extension, entry_points in sorted(gles2_extension_requirements.items()):
431        gles2_extensions_data.append(
432            format_extension_requirements_lines(extension, entry_points, "gles2"))
433
434    both_extensions_data = []
435    for extension, entry_points in sorted(both_extension_requirements.items()):
436        both_extensions_data.append(
437            format_extension_requirements_lines(extension, entry_points, "gles2|gl"))
438
439    dispatch_table_source = dispatch_table_source_template.format(
440        script_name=os.path.basename(sys.argv[0]),
441        data_source_name=data_source_name,
442        file_name=dispatch_source_path,
443        gl_data="\n\n".join(gl_data),
444        gl_extensions_data="\n\n".join(gl_extensions_data),
445        gles2_data="\n\n".join(gles2_data),
446        gles2_extensions_data="\n\n".join(gles2_extensions_data),
447        both_extensions_data="\n\n".join(both_extensions_data),
448        gl_null_data="\n\n".join(nullify(gl_data)),
449        gl_null_extensions_data="\n\n".join(nullify(gl_extensions_data)),
450        gles2_null_data="\n\n".join(nullify(gles2_data)),
451        gles2_null_extensions_data="\n\n".join(nullify(gles2_extensions_data)),
452        both_null_extensions_data="\n\n".join(nullify(both_extensions_data)))
453
454    with open(dispatch_source_path, "w") as out:
455        out.write(dispatch_table_source)
456
457    # Generate the NULL/stub entry points.
458    # Process the whole set of commands
459
460    command_defs = {}
461    command_decls = {}
462
463    for command in xml_root.findall('commands/command'):
464        proto = command.find('proto')
465        command_name = proto.find('name').text
466        entry = ''.join(proto.itertext())
467        return_type = entry[:-len(command_name)]
468        entry = return_type + ' INTERNAL_GL_APIENTRY ' + entry[len(return_type):] + 'NULL('
469
470        param_text = [format_param(param) for param in command.findall('param')]
471        entry += ', '.join(param_text) + ')'
472
473        command_decls[command_name] = entry + ';'
474
475        entry += '\n{\n'
476        if return_type != 'void ':
477            entry += '    return static_cast<' + return_type + '>(0);\n'
478        entry += '}'
479
480        command_defs[command_name] = entry
481
482    null_decls = [command_decls[entry_point] for entry_point in sorted(all_entry_points)]
483    null_stubs = [command_defs[entry_point] for entry_point in sorted(all_entry_points)]
484
485    null_functions_header = null_functions_header_template.format(
486        script_name=os.path.basename(sys.argv[0]),
487        data_source_name=data_source_name,
488        file_name=null_functions_header_path,
489        table_data="\n".join(null_decls))
490
491    with open(null_functions_header_path, "w") as out:
492        out.write(null_functions_header)
493
494    null_functions_source = null_functions_source_template.format(
495        script_name=os.path.basename(sys.argv[0]),
496        data_source_name=data_source_name,
497        file_name=null_functions_source_path,
498        table_data="\n\n".join(null_stubs))
499
500    with open(null_functions_source_path, "w") as out:
501        out.write(null_functions_source)
502    return 0
503
504
505if __name__ == '__main__':
506    sys.exit(main())
507