controls: Add vendor control/property support to generation scripts

Add support for vendor-specific controls and properties to libcamera.
The controls/properties are defined by a "vendor" tag in the YAML
control description file, for example:

vendor: rpi
controls:
  - MyExampleControl:
      type: string
      description: |
        Test for libcamera vendor-specific controls.

This will now generate a control id in the libcamera::controls::rpi
namespace, ensuring no id conflict between different vendors, core or
draft libcamera controls. Similarly, a ControlIdMap control is generated
in the libcamera::controls::rpi namespace.

A #define LIBCAMERA_HAS_RPI_VENDOR_CONTROLS is also generated to allow
applications to conditionally compile code if the specific vendor
controls are present. For the python bindings, the control is available
with libcamera.controls.rpi.MyExampleControl. The above controls
example applies similarly to properties.

Existing libcamera controls defined in control_ids.yaml are given the
"libcamera" vendor tag.

A new --mode flag is added to gen-controls.py to specify the mode of
operation, either 'controls' or 'properties' to allow the code generator
to correctly set the #define string.

As a drive-by, sort and redefine the output command line argument in
gen-controls.py and gen-py-controls.py to ('--output', '-o') for
consistency.

Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
Naushir Patuck 2023-11-08 11:09:51 +00:00
parent 61f6b37242
commit bd6658943a
12 changed files with 176 additions and 73 deletions

View file

@ -32,6 +32,8 @@ ${draft_controls}
} /* namespace draft */
${vendor_controls}
} /* namespace controls */
} /* namespace libcamera */

View file

@ -32,20 +32,21 @@ install_headers(libcamera_public_headers,
libcamera_headers_install_dir = get_option('includedir') / libcamera_include_dir
# control_ids.h and property_ids.h
control_source_files = [
'control_ids',
'property_ids',
]
# control_ids.h and property_ids.h and associated modes
control_source_files = {
'control_ids': 'controls',
'property_ids': 'properties',
}
control_headers = []
foreach header : control_source_files
foreach header, mode : control_source_files
input_files = files('../../src/libcamera/' + header +'.yaml', header + '.h.in')
control_headers += custom_target(header + '_h',
input : input_files,
output : header + '.h',
command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'],
command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@',
'--mode', mode],
install : true,
install_dir : libcamera_headers_install_dir)
endforeach

View file

@ -31,6 +31,8 @@ ${draft_controls}
extern const ControlIdMap properties;
${vendor_controls}
} /* namespace properties */
} /* namespace libcamera */

View file

@ -33,6 +33,8 @@ ${draft_controls_doc}
} /* namespace draft */
${vendor_controls_doc}
#ifndef __DOXYGEN__
/*
* Keep the controls definitions hidden from doxygen as it incorrectly parses
@ -45,6 +47,9 @@ namespace draft {
${draft_controls_def}
} /* namespace draft */
${vendor_controls_def}
#endif
/**

View file

@ -6,6 +6,7 @@
---
# Unless otherwise stated, all controls are bi-directional, i.e. they can be
# set through Request::controls() and returned out through Request::metadata().
vendor: libcamera
controls:
- AeEnable:
type: bool

View file

@ -127,12 +127,13 @@ endif
control_sources = []
foreach source : control_source_files
foreach source, mode : control_source_files
input_files = files(source +'.yaml', source + '.cpp.in')
control_sources += custom_target(source + '_cpp',
input : input_files,
output : source + '.cpp',
command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'])
command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@',
'--mode', mode])
endforeach
libcamera_sources += control_sources

View file

@ -32,6 +32,8 @@ ${draft_controls_doc}
} /* namespace draft */
${vendor_controls_doc}
#ifndef __DOXYGEN__
/*
* Keep the properties definitions hidden from doxygen as it incorrectly parses
@ -44,6 +46,9 @@ namespace draft {
${draft_controls_def}
} /* namespace draft */
${vendor_controls_def}
#endif
/**

View file

@ -4,6 +4,7 @@
#
%YAML 1.1
---
vendor: libcamera
controls:
- Location:
type: int32_t

View file

@ -24,45 +24,59 @@ def find_common_prefix(strings):
def generate_py(controls, mode):
out = ''
for ctrl in controls:
name, ctrl = ctrl.popitem()
vendors_class_def = []
vendor_defs = []
vendors = []
for vendor, ctrl_list in controls.items():
for ctrls in ctrl_list:
name, ctrl = ctrls.popitem()
if ctrl.get('draft'):
ns = 'libcamera::{}::draft::'.format(mode)
container = 'draft'
else:
ns = 'libcamera::{}::'.format(mode)
container = 'controls'
if vendor not in vendors and vendor != 'libcamera':
vendors_class_def.append('class Py{}Controls\n{{\n}};\n'.format(vendor))
vendor_defs.append('\tauto {} = py::class_<Py{}Controls>(controls, \"{}\");'.format(vendor, vendor, vendor))
vendors.append(vendor)
out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
enum = ctrl.get('enum')
if not enum:
continue
cpp_enum = name + 'Enum'
out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
if mode == 'controls':
# Adjustments for controls
if name == 'LensShadingMapMode':
prefix = 'LensShadingMapMode'
if ctrl.get('draft'):
ns = 'libcamera::{}::draft::'.format(mode)
container = 'draft'
elif vendor != 'libcamera':
ns = 'libcamera::{}::{}::'.format(mode, vendor)
container = vendor
else:
ns = 'libcamera::{}::'.format(mode)
container = 'controls'
out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
enum = ctrl.get('enum')
if not enum:
continue
cpp_enum = name + 'Enum'
out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
if mode == 'controls':
# Adjustments for controls
if name == 'LensShadingMapMode':
prefix = 'LensShadingMapMode'
else:
prefix = find_common_prefix([e['name'] for e in enum])
else:
# Adjustments for properties
prefix = find_common_prefix([e['name'] for e in enum])
else:
# Adjustments for properties
prefix = find_common_prefix([e['name'] for e in enum])
for entry in enum:
cpp_enum = entry['name']
py_enum = entry['name'][len(prefix):]
for entry in enum:
cpp_enum = entry['name']
py_enum = entry['name'][len(prefix):]
out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
out += '\t;\n\n'
out += '\t;\n\n'
return {'controls': out}
return {'controls': out,
'vendors_class_def': '\n'.join(vendors_class_def),
'vendors_defs': '\n'.join(vendor_defs)}
def fill_template(template, data):
@ -75,14 +89,14 @@ def fill_template(template, data):
def main(argv):
# Parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('-o', dest='output', metavar='file', type=str,
parser.add_argument('--mode', '-m', type=str, required=True,
help='Mode is either "controls" or "properties"')
parser.add_argument('--output', '-o', metavar='file', type=str,
help='Output file name. Defaults to standard output if not specified.')
parser.add_argument('input', type=str,
help='Input file name.')
parser.add_argument('template', type=str,
help='Template file name.')
parser.add_argument('--mode', type=str, required=True,
help='Mode is either "controls" or "properties"')
args = parser.parse_args(argv[1:])
if args.mode not in ['controls', 'properties']:
@ -90,7 +104,10 @@ def main(argv):
return -1
data = open(args.input, 'rb').read()
controls = yaml.safe_load(data)['controls']
controls = {}
vendor = yaml.safe_load(data)['vendor']
controls[vendor] = yaml.safe_load(data)['controls']
data = generate_py(controls, args.mode)

View file

@ -21,10 +21,13 @@ class PyDraftControls
{
};
${vendors_class_def}
void init_py_controls_generated(py::module& m)
{
auto controls = py::class_<PyControls>(m, "controls");
auto draft = py::class_<PyDraftControls>(controls, "draft");
${vendors_defs}
${controls}
}

View file

@ -21,10 +21,13 @@ class PyDraftProperties
{
};
${vendors_class_def}
void init_py_properties_generated(py::module& m)
{
auto controls = py::class_<PyProperties>(m, "properties");
auto draft = py::class_<PyDraftProperties>(controls, "draft");
${vendors_defs}
${controls}
}

View file

@ -35,11 +35,12 @@ class ControlEnum(object):
class Control(object):
def __init__(self, name, data):
def __init__(self, name, data, vendor):
self.__name = name
self.__data = data
self.__enum_values = None
self.__size = None
self.__vendor = vendor
enum_values = data.get('enum')
if enum_values is not None:
@ -89,6 +90,11 @@ class Control(object):
"""Is the control a draft control"""
return self.__data.get('draft') is not None
@property
def vendor(self):
"""The vendor string, or None"""
return self.__vendor
@property
def name(self):
"""The control name (CamelCase)"""
@ -145,15 +151,18 @@ ${description}
enum_values_start = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values = {''')
enum_values_values = string.Template('''\tstatic_cast<int32_t>(${name}),''')
ctrls_doc = []
ctrls_def = []
draft_ctrls_doc = []
draft_ctrls_def = []
ctrls_doc = {}
ctrls_def = {}
ctrls_map = []
for ctrl in controls:
id_name = snake_case(ctrl.name).upper()
vendor = 'draft' if ctrl.is_draft else ctrl.vendor
if vendor not in ctrls_doc:
ctrls_doc[vendor] = []
ctrls_def[vendor] = []
info = {
'name': ctrl.name,
'type': ctrl.type,
@ -161,11 +170,8 @@ ${description}
'id_name': id_name,
}
target_doc = ctrls_doc
target_def = ctrls_def
if ctrl.is_draft:
target_doc = draft_ctrls_doc
target_def = draft_ctrls_def
target_doc = ctrls_doc[vendor]
target_def = ctrls_def[vendor]
if ctrl.is_enum:
enum_doc = []
@ -203,39 +209,68 @@ ${description}
ctrls_map.append('\t{ ' + id_name + ', &' + ctrl.q_name + ' },')
vendor_ctrl_doc_sub = []
vendor_ctrl_template = string.Template('''
/**
* \\brief Namespace for ${vendor} controls
*/
namespace ${vendor} {
${vendor_controls_str}
} /* namespace ${vendor} */''')
for vendor in [v for v in ctrls_doc.keys() if v not in ['libcamera', 'draft']]:
vendor_ctrl_doc_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n\n'.join(ctrls_doc[vendor])}))
vendor_ctrl_def_sub = []
for vendor in [v for v in ctrls_def.keys() if v not in ['libcamera', 'draft']]:
vendor_ctrl_def_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n'.join(ctrls_def[vendor])}))
return {
'controls_doc': '\n\n'.join(ctrls_doc),
'controls_def': '\n'.join(ctrls_def),
'draft_controls_doc': '\n\n'.join(draft_ctrls_doc),
'draft_controls_def': '\n\n'.join(draft_ctrls_def),
'controls_doc': '\n\n'.join(ctrls_doc['libcamera']),
'controls_def': '\n'.join(ctrls_def['libcamera']),
'draft_controls_doc': '\n\n'.join(ctrls_doc['draft']),
'draft_controls_def': '\n\n'.join(ctrls_def['draft']),
'controls_map': '\n'.join(ctrls_map),
'vendor_controls_doc': '\n'.join(vendor_ctrl_doc_sub),
'vendor_controls_def': '\n'.join(vendor_ctrl_def_sub),
}
def generate_h(controls):
def generate_h(controls, mode):
enum_template_start = string.Template('''enum ${name}Enum {''')
enum_value_template = string.Template('''\t${name} = ${value},''')
enum_values_template = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values;''')
template = string.Template('''extern const Control<${type}> ${name};''')
ctrls = []
draft_ctrls = []
ids = []
id_value = 1
ctrls = {}
ids = {}
id_value = {}
for ctrl in controls:
id_name = snake_case(ctrl.name).upper()
ids.append('\t' + id_name + ' = ' + str(id_value) + ',')
vendor = 'draft' if ctrl.is_draft else ctrl.vendor
if vendor not in ctrls:
ids[vendor] = []
id_value[vendor] = 1
ctrls[vendor] = []
# Core and draft controls use the same ID value
target_ids = ids['libcamera'] if vendor in ['libcamera', 'draft'] else ids[vendor]
target_ids.append('\t' + id_name + ' = ' + str(id_value[vendor]) + ',')
info = {
'name': ctrl.name,
'type': ctrl.type,
}
target_ctrls = ctrls
target_ctrls = ctrls['libcamera']
if ctrl.is_draft:
target_ctrls = draft_ctrls
target_ctrls = ctrls['draft']
elif vendor != 'libcamera':
target_ctrls = ctrls[vendor]
if ctrl.is_enum:
target_ctrls.append(enum_template_start.substitute(info))
@ -257,12 +292,35 @@ def generate_h(controls):
target_ctrls.append(enum_values_template.substitute(values_info))
target_ctrls.append(template.substitute(info))
id_value += 1
id_value[vendor] += 1
vendor_template = string.Template('''
namespace ${vendor} {
#define LIBCAMERA_HAS_${vendor_def}_VENDOR_${mode}
enum {
${vendor_enums}
};
${vendor_controls}
} /* namespace ${vendor} */
''')
vendor_sub = []
for vendor in [v for v in ctrls.keys() if v not in ['libcamera', 'draft']]:
vendor_sub.append(vendor_template.substitute({'mode': mode.upper(),
'vendor': vendor,
'vendor_def': vendor.upper(),
'vendor_enums': '\n'.join(ids[vendor]),
'vendor_controls': '\n'.join(ctrls[vendor])}))
return {
'ids': '\n'.join(ids),
'controls': '\n'.join(ctrls),
'draft_controls': '\n'.join(draft_ctrls)
'ids': '\n'.join(ids['libcamera']),
'controls': '\n'.join(ctrls['libcamera']),
'draft_controls': '\n'.join(ctrls['draft']),
'vendor_controls': '\n'.join(vendor_sub)
}
@ -278,22 +336,26 @@ def main(argv):
# Parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('-o', dest='output', metavar='file', type=str,
parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],
help='Mode of operation')
parser.add_argument('--output', '-o', metavar='file', type=str,
help='Output file name. Defaults to standard output if not specified.')
parser.add_argument('input', type=str,
help='Input file name.')
parser.add_argument('template', type=str,
help='Template file name.')
args = parser.parse_args(argv[1:])
data = open(args.input, 'rb').read()
vendor = yaml.safe_load(data)['vendor']
controls = yaml.safe_load(data)['controls']
controls = [Control(*ctrl.popitem()) for ctrl in controls]
controls = [Control(*ctrl.popitem(), vendor) for ctrl in controls]
if args.template.endswith('.cpp.in'):
data = generate_cpp(controls)
elif args.template.endswith('.h.in'):
data = generate_h(controls)
data = generate_h(controls, args.mode)
else:
raise RuntimeError('Unknown template type')