diff options
Diffstat (limited to 'utils/codegen')
-rw-r--r-- | utils/codegen/controls.py | 32 | ||||
-rwxr-xr-x | utils/codegen/gen-controls.py | 2 | ||||
-rwxr-xr-x | utils/codegen/gen-gst-controls.py | 183 | ||||
-rwxr-xr-x | utils/codegen/gen-tp-header.py | 4 | ||||
-rw-r--r-- | utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl | 5 | ||||
-rw-r--r-- | utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl | 14 | ||||
-rw-r--r-- | utils/codegen/ipc/generators/mojom_libcamera_generator.py | 2 | ||||
-rw-r--r-- | utils/codegen/meson.build | 1 |
8 files changed, 235 insertions, 8 deletions
diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py index 7bafee59..e5161048 100644 --- a/utils/codegen/controls.py +++ b/utils/codegen/controls.py @@ -28,7 +28,7 @@ class ControlEnum(object): class Control(object): - def __init__(self, name, data, vendor): + def __init__(self, name, data, vendor, mode): self.__name = name self.__data = data self.__enum_values = None @@ -60,6 +60,16 @@ class Control(object): self.__size = num_elems + if mode == 'properties': + self.__direction = 'out' + else: + direction = self.__data.get('direction') + if direction is None: + raise RuntimeError(f'Control `{self.__name}` missing required field `direction`') + if direction not in ['in', 'out', 'inout']: + raise RuntimeError(f'Control `{self.__name}` direction `{direction}` is invalid; must be one of `in`, `out`, or `inout`') + self.__direction = direction + @property def description(self): """The control description""" @@ -110,3 +120,23 @@ class Control(object): return f"Span<const {typ}, {self.__size}>" else: return f"Span<const {typ}>" + + @property + def direction(self): + in_flag = 'ControlId::Direction::In' + out_flag = 'ControlId::Direction::Out' + + if self.__direction == 'inout': + return f'{in_flag} | {out_flag}' + if self.__direction == 'in': + return in_flag + if self.__direction == 'out': + return out_flag + + @property + def element_type(self): + return self.__data.get('type') + + @property + def size(self): + return self.__size diff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py index 3034e9a5..59b716c1 100755 --- a/utils/codegen/gen-controls.py +++ b/utils/codegen/gen-controls.py @@ -71,7 +71,7 @@ def main(argv): ctrls = controls.setdefault(vendor, []) for i, ctrl in enumerate(data['controls']): - ctrl = Control(*ctrl.popitem(), vendor) + ctrl = Control(*ctrl.popitem(), vendor, args.mode) ctrls.append(extend_control(ctrl, i, ranges)) # Sort the vendors by range numerical value diff --git a/utils/codegen/gen-gst-controls.py b/utils/codegen/gen-gst-controls.py new file mode 100755 index 00000000..07af7653 --- /dev/null +++ b/utils/codegen/gen-gst-controls.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2019, Google Inc. +# Copyright (C) 2024, Jaslo Ziska +# +# Authors: +# Laurent Pinchart <laurent.pinchart@ideasonboard.com> +# Jaslo Ziska <jaslo@ziska.de> +# +# Generate gstreamer control properties from YAML + +import argparse +import jinja2 +import re +import sys +import yaml + +from controls import Control + + +exposed_controls = [ + 'AeMeteringMode', 'AeConstraintMode', 'AeExposureMode', + 'ExposureValue', 'ExposureTime', 'ExposureTimeMode', + 'AnalogueGain', 'AnalogueGainMode', 'AeFlickerPeriod', + 'Brightness', 'Contrast', 'AwbEnable', 'AwbMode', 'ColourGains', + 'Saturation', 'Sharpness', 'ColourCorrectionMatrix', 'ScalerCrop', + 'DigitalGain', 'AfMode', 'AfRange', 'AfSpeed', 'AfMetering', 'AfWindows', + 'LensPosition', 'Gamma', +] + + +def find_common_prefix(strings): + prefix = strings[0] + + for string in strings[1:]: + while string[:len(prefix)] != prefix and prefix: + prefix = prefix[:len(prefix) - 1] + if not prefix: + break + + return prefix + + +def format_description(description): + # Substitute doxygen keywords \sa (see also) and \todo + description = re.sub(r'\\sa((?: \w+)+)', + lambda match: 'See also: ' + ', '.join( + map(kebab_case, match.group(1).strip().split(' ')) + ) + '.', description) + description = re.sub(r'\\todo', 'Todo:', description) + + description = description.strip().split('\n') + return '\n'.join([ + '"' + line.replace('\\', r'\\').replace('"', r'\"') + ' "' for line in description if line + ]).rstrip() + + +# Custom filter to allow indenting by a string prior to Jinja version 3.0 +# +# This function can be removed and the calls to indent_str() replaced by the +# built-in indent() filter when dropping Jinja versions older than 3.0 +def indent_str(s, indention): + s += '\n' + + lines = s.splitlines() + rv = lines.pop(0) + + if lines: + rv += '\n' + '\n'.join( + indention + line if line else line for line in lines + ) + + return rv + + +def snake_case(s): + return ''.join([ + c.isupper() and ('_' + c.lower()) or c for c in s + ]).strip('_') + + +def kebab_case(s): + return snake_case(s).replace('_', '-') + + +def extend_control(ctrl): + if ctrl.vendor != 'libcamera': + ctrl.namespace = f'{ctrl.vendor}::' + ctrl.vendor_prefix = f'{ctrl.vendor}-' + else: + ctrl.namespace = '' + ctrl.vendor_prefix = '' + + ctrl.is_array = ctrl.size is not None + + if ctrl.is_enum: + # Remove common prefix from enum variant names + prefix = find_common_prefix([enum.name for enum in ctrl.enum_values]) + for enum in ctrl.enum_values: + enum.gst_name = kebab_case(enum.name.removeprefix(prefix)) + + ctrl.gtype = 'enum' + ctrl.default = '0' + elif ctrl.element_type == 'bool': + ctrl.gtype = 'boolean' + ctrl.default = 'false' + elif ctrl.element_type == 'float': + ctrl.gtype = 'float' + ctrl.default = '0' + ctrl.min = '-G_MAXFLOAT' + ctrl.max = 'G_MAXFLOAT' + elif ctrl.element_type == 'int32_t': + ctrl.gtype = 'int' + ctrl.default = '0' + ctrl.min = 'G_MININT' + ctrl.max = 'G_MAXINT' + elif ctrl.element_type == 'int64_t': + ctrl.gtype = 'int64' + ctrl.default = '0' + ctrl.min = 'G_MININT64' + ctrl.max = 'G_MAXINT64' + elif ctrl.element_type == 'uint8_t': + ctrl.gtype = 'uchar' + ctrl.default = '0' + ctrl.min = '0' + ctrl.max = 'G_MAXUINT8' + elif ctrl.element_type == 'Rectangle': + ctrl.is_rectangle = True + ctrl.default = '0' + ctrl.min = '0' + ctrl.max = 'G_MAXINT' + else: + raise RuntimeError(f'The type `{ctrl.element_type}` is unknown') + + return ctrl + + +def main(argv): + # Parse command line arguments + parser = argparse.ArgumentParser() + parser.add_argument('--output', '-o', metavar='file', type=str, + help='Output file name. Defaults to standard output if not specified.') + parser.add_argument('--template', '-t', dest='template', type=str, required=True, + help='Template file name.') + parser.add_argument('input', type=str, nargs='+', + help='Input file name.') + + args = parser.parse_args(argv[1:]) + + controls = {} + for input in args.input: + data = yaml.safe_load(open(input, 'rb').read()) + + vendor = data['vendor'] + ctrls = controls.setdefault(vendor, []) + + for ctrl in data['controls']: + ctrl = Control(*ctrl.popitem(), vendor, mode='controls') + + if ctrl.name in exposed_controls: + ctrls.append(extend_control(ctrl)) + + data = {'controls': list(controls.items())} + + env = jinja2.Environment() + env.filters['format_description'] = format_description + env.filters['indent_str'] = indent_str + env.filters['snake_case'] = snake_case + env.filters['kebab_case'] = kebab_case + template = env.from_string(open(args.template, 'r', encoding='utf-8').read()) + string = template.render(data) + + if args.output: + with open(args.output, 'w', encoding='utf-8') as output: + output.write(string) + else: + sys.stdout.write(string) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/utils/codegen/gen-tp-header.py b/utils/codegen/gen-tp-header.py index 83606c32..6769c7ce 100755 --- a/utils/codegen/gen-tp-header.py +++ b/utils/codegen/gen-tp-header.py @@ -6,7 +6,6 @@ # # Generate header file to contain lttng tracepoints -import datetime import jinja2 import pathlib import os @@ -20,7 +19,6 @@ def main(argv): output = argv[2] template = argv[3] - year = datetime.datetime.now().year path = pathlib.Path(output).absolute().relative_to(argv[1]) source = '' @@ -28,7 +26,7 @@ def main(argv): source += open(fname, 'r', encoding='utf-8').read() + '\n\n' template = jinja2.Template(open(template, 'r', encoding='utf-8').read()) - string = template.render(year=year, path=path, source=source) + string = template.render(path=path, source=source) f = open(output, 'w', encoding='utf-8').write(string) diff --git a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl index 7f2d0810..3942e570 100644 --- a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl @@ -15,8 +15,13 @@ #pragma once {% if has_map %}#include <map>{% endif %} +{% if has_string %}#include <string>{% endif %} {% if has_array %}#include <vector>{% endif %} +#include <libcamera/controls.h> +#include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> + #include <libcamera/ipa/ipa_interface.h> namespace libcamera { diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl index 4d88a3d7..5d70ea6a 100644 --- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl @@ -14,12 +14,20 @@ #pragma once -#include <libcamera/ipa/core_ipa_interface.h> -#include <libcamera/ipa/ipa_interface.h> - {% if has_map %}#include <map>{% endif %} +{% if has_string %}#include <string>{% endif %} {% if has_array %}#include <vector>{% endif %} +#include <libcamera/base/flags.h> +#include <libcamera/base/signal.h> + +#include <libcamera/controls.h> +#include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> + +#include <libcamera/ipa/core_ipa_interface.h> +#include <libcamera/ipa/ipa_interface.h> + namespace libcamera { {%- if has_namespace %} {% for ns in namespace %} diff --git a/utils/codegen/ipc/generators/mojom_libcamera_generator.py b/utils/codegen/ipc/generators/mojom_libcamera_generator.py index b8209e51..d9c620a0 100644 --- a/utils/codegen/ipc/generators/mojom_libcamera_generator.py +++ b/utils/codegen/ipc/generators/mojom_libcamera_generator.py @@ -467,6 +467,7 @@ class Generator(generator.Generator): 'enums': self.module.enums, 'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0, 'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0, + 'has_string': len([x for x in self.module.kinds.keys() if x[0] == 's']) > 0, 'has_namespace': self.module.mojom_namespace != '', 'interface_event': GetEventInterface(self.module.interfaces), 'interface_main': GetMainInterface(self.module.interfaces), @@ -486,6 +487,7 @@ class Generator(generator.Generator): 'enums_gen_header': [x for x in self.module.enums if x.attributes is None or 'skipHeader' not in x.attributes], 'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0, 'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0, + 'has_string': len([x for x in self.module.kinds.keys() if x[0] == 's']) > 0, 'structs_gen_header': [x for x in self.module.structs if x.attributes is None or 'skipHeader' not in x.attributes], 'structs_gen_serializer': [x for x in self.module.structs if x.attributes is None or 'skipSerdes' not in x.attributes], } diff --git a/utils/codegen/meson.build b/utils/codegen/meson.build index adf33bba..904dd66d 100644 --- a/utils/codegen/meson.build +++ b/utils/codegen/meson.build @@ -11,6 +11,7 @@ py_modules += ['jinja2', 'yaml'] gen_controls = files('gen-controls.py') gen_formats = files('gen-formats.py') +gen_gst_controls = files('gen-gst-controls.py') gen_header = files('gen-header.sh') gen_ipa_pub_key = files('gen-ipa-pub-key.py') gen_tracepoints = files('gen-tp-header.py') |