mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-15 03:41:34 +00:00
This makes the OpenGL entry points loaded at Adapter creation from the getProcAddress passed in the DiscoveryOptions and update all GL calls in the backend to go through the new OpenGLFunctions object. A code generator is added that generates the function loader and list of GL procs from Khronos' gl.xml file but we can't get rid of glad yet because it is used to have the PROC typedefs and enum values. BUG=dawn:165 Change-Id: I2a583d79752f55877fa4190846f5be16cf91651a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/7983 Commit-Queue: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
204 lines
7.3 KiB
Python
204 lines
7.3 KiB
Python
#!/usr/bin/env python2
|
|
# Copyright 2019 The Dawn Authors
|
|
#
|
|
# 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 argparse, json, os, re, sys
|
|
from collections import namedtuple
|
|
|
|
# The interface that must be implemented by generators.
|
|
class Generator:
|
|
def get_description(self):
|
|
return ""
|
|
|
|
def add_commandline_arguments(self, parser):
|
|
pass
|
|
|
|
def get_file_renders(self, args):
|
|
return []
|
|
|
|
def get_dependencies(self, args):
|
|
return []
|
|
|
|
FileRender = namedtuple('FileRender', ['template', 'output', 'params_dicts'])
|
|
|
|
# Try using an additional python path from the arguments if present. This
|
|
# isn't done through the regular argparse because PreprocessingLoader uses
|
|
# jinja2 in the global scope before "main" gets to run.
|
|
kExtraPythonPath = '--extra-python-path'
|
|
if kExtraPythonPath in sys.argv:
|
|
path = sys.argv[sys.argv.index(kExtraPythonPath) + 1]
|
|
sys.path.insert(1, path)
|
|
|
|
import jinja2
|
|
|
|
# A custom Jinja2 template loader that removes the extra indentation
|
|
# of the template blocks so that the output is correctly indented
|
|
class _PreprocessingLoader(jinja2.BaseLoader):
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def get_source(self, environment, template):
|
|
path = os.path.join(self.path, template)
|
|
if not os.path.exists(path):
|
|
raise jinja2.TemplateNotFound(template)
|
|
mtime = os.path.getmtime(path)
|
|
with open(path) as f:
|
|
source = self.preprocess(f.read())
|
|
return source, path, lambda: mtime == os.path.getmtime(path)
|
|
|
|
blockstart = re.compile('{%-?\s*(if|for|block)[^}]*%}')
|
|
blockend = re.compile('{%-?\s*end(if|for|block)[^}]*%}')
|
|
|
|
def preprocess(self, source):
|
|
lines = source.split('\n')
|
|
|
|
# Compute the current indentation level of the template blocks and remove their indentation
|
|
result = []
|
|
indentation_level = 0
|
|
|
|
for line in lines:
|
|
# The capture in the regex adds one element per block start or end so we divide by two
|
|
# there is also an extra line chunk corresponding to the line end, so we substract it.
|
|
numends = (len(self.blockend.split(line)) - 1) // 2
|
|
indentation_level -= numends
|
|
|
|
result.append(self.remove_indentation(line, indentation_level))
|
|
|
|
numstarts = (len(self.blockstart.split(line)) - 1) // 2
|
|
indentation_level += numstarts
|
|
|
|
return '\n'.join(result) + '\n'
|
|
|
|
def remove_indentation(self, line, n):
|
|
for _ in range(n):
|
|
if line.startswith(' '):
|
|
line = line[4:]
|
|
elif line.startswith('\t'):
|
|
line = line[1:]
|
|
else:
|
|
assert(line.strip() == '')
|
|
return line
|
|
|
|
_FileOutput = namedtuple('FileOutput', ['name', 'content'])
|
|
|
|
def _do_renders(renders, template_dir):
|
|
loader = _PreprocessingLoader(template_dir)
|
|
env = jinja2.Environment(loader=loader, lstrip_blocks=True, trim_blocks=True, line_comment_prefix='//*')
|
|
|
|
def do_assert(expr):
|
|
assert expr
|
|
return ''
|
|
|
|
def debug(text):
|
|
print(text)
|
|
|
|
base_params = {
|
|
'enumerate': enumerate,
|
|
'format': format,
|
|
'len': len,
|
|
'debug': debug,
|
|
'assert': do_assert,
|
|
}
|
|
|
|
outputs = []
|
|
for render in renders:
|
|
params = {}
|
|
params.update(base_params)
|
|
for param_dict in render.params_dicts:
|
|
params.update(param_dict)
|
|
content = env.get_template(render.template).render(**params)
|
|
outputs.append(_FileOutput(render.output, content))
|
|
|
|
return outputs
|
|
|
|
# Compute the list of imported, non-system Python modules.
|
|
# It assumes that any path outside of Dawn's root directory is system.
|
|
def _compute_python_dependencies():
|
|
dawn_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
|
|
module_paths = (module.__file__ for module in sys.modules.values()
|
|
if module and hasattr(module, '__file__'))
|
|
|
|
paths = set()
|
|
for path in module_paths:
|
|
path = os.path.abspath(path)
|
|
|
|
if not path.startswith(dawn_root):
|
|
continue
|
|
|
|
if (path.endswith('.pyc')
|
|
or (path.endswith('c') and not os.path.splitext(path)[1])):
|
|
path = path[:-1]
|
|
|
|
paths.add(path)
|
|
|
|
return paths
|
|
|
|
def run_generator(generator):
|
|
parser = argparse.ArgumentParser(
|
|
description = generator.get_description(),
|
|
formatter_class = argparse.ArgumentDefaultsHelpFormatter
|
|
)
|
|
|
|
generator.add_commandline_arguments(parser);
|
|
parser.add_argument('-t', '--template-dir', default='templates', type=str, help='Directory with template files.')
|
|
parser.add_argument(kExtraPythonPath, default=None, type=str, help='Additional python path to set before loading Jinja2')
|
|
parser.add_argument('--output-json-tarball', default=None, type=str, help='Name of the "JSON tarball" to create (tar is too annoying to use in python).')
|
|
parser.add_argument('--depfile', default=None, type=str, help='Name of the Ninja depfile to create for the JSON tarball')
|
|
parser.add_argument('--expected-outputs-file', default=None, type=str, help="File to compare outputs with and fail if it doesn't match")
|
|
|
|
args = parser.parse_args()
|
|
|
|
renders = generator.get_file_renders(args);
|
|
|
|
# The caller wants to assert that the outputs are what it expects.
|
|
# Load the file and compare with our renders.
|
|
if args.expected_outputs_file != None:
|
|
with open(args.expected_outputs_file) as f:
|
|
expected = set([line.strip() for line in f.readlines()])
|
|
|
|
actual = set()
|
|
actual.update([render.output for render in renders])
|
|
|
|
if actual != expected:
|
|
print("Wrong expected outputs, caller expected:\n " + repr(list(expected)))
|
|
print("Actual output:\n " + repr(list(actual)))
|
|
return 1
|
|
|
|
# Add a any extra Python path before importing Jinja2 so invokers can point
|
|
# to a checkout of Jinja2 and note require it to be installed on the system
|
|
if args.extra_python_path != None:
|
|
sys.path.insert(1, args.extra_python_path)
|
|
import jinja2
|
|
|
|
outputs = _do_renders(renders, args.template_dir)
|
|
|
|
# Output the tarball and its depfile
|
|
if args.output_json_tarball != None:
|
|
json_root = {}
|
|
for output in outputs:
|
|
json_root[output.name] = output.content
|
|
|
|
with open(args.output_json_tarball, 'w') as f:
|
|
f.write(json.dumps(json_root))
|
|
|
|
# Output a list of all dependencies for the tarball for Ninja.
|
|
if args.depfile != None:
|
|
dependencies = generator.get_dependencies(args)
|
|
dependencies += [args.template_dir + os.path.sep + render.template for render in renders]
|
|
dependencies += _compute_python_dependencies()
|
|
|
|
with open(args.depfile, 'w') as f:
|
|
f.write(args.output_json_tarball + ": " + " ".join(dependencies))
|