summaryrefslogtreecommitdiff
path: root/utils/codegen
diff options
context:
space:
mode:
Diffstat (limited to 'utils/codegen')
-rw-r--r--utils/codegen/controls.py32
-rwxr-xr-xutils/codegen/gen-controls.py2
-rwxr-xr-xutils/codegen/gen-gst-controls.py183
-rwxr-xr-xutils/codegen/gen-tp-header.py4
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl5
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl14
-rw-r--r--utils/codegen/ipc/generators/mojom_libcamera_generator.py2
-rw-r--r--utils/codegen/meson.build1
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')