From patchwork Fri Nov 10 10:59:58 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Naushir Patuck X-Patchwork-Id: 19194 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D3AB0C3285 for ; Fri, 10 Nov 2023 11:00:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 51F60629C8; Fri, 10 Nov 2023 12:00:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1699614001; bh=dlfiNxQIcLROOxPnUDt9+UpfHMH7B8/7kT1KXwIba+0=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=1DkthgIxL2GzZQE8irmaYNjRRI+XeKeAl1OmY8YteAgcqhFhquyFvCl4muv6caaox Kr7bmsnvN5f0gRpa4jqaS+7IIZU93S7eE3lEirbwkch4FJeI2BPfknyWrOAVbV2TWP iSaXoxROIbOLy5qgTjIrT+FlyNuAuKKeQ5UntEick0D1elYe2WX+pwNgAkM6zcc2cK kuvdpVosYBKaf0ksdYxtkh6UZab8ahEeikzk8R9mtfMGv8QosAOExRsmPvbowYn0ic br6cf3GS7nPxgsAHCbdBZECSmfvUd0HCi3NUMFgDWU/7mMyyPSCZj5Y4NWZhcl9zQ2 IaLzEJBHMbQIg== Received: from mail-lj1-x235.google.com (mail-lj1-x235.google.com [IPv6:2a00:1450:4864:20::235]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D35D6629BD for ; Fri, 10 Nov 2023 11:59:58 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="c6HfCTb3"; dkim-atps=neutral Received: by mail-lj1-x235.google.com with SMTP id 38308e7fff4ca-2c503dbe50dso25258391fa.1 for ; Fri, 10 Nov 2023 02:59:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; t=1699613997; x=1700218797; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ng52OPkZn77AerY9h5LP2IbzTh0qdlf5lJORgUvbVBo=; b=c6HfCTb3zMSmhVh/izo7SpL1yz7TYprHN3nVsxlMR4XhOWI0DCR7xSdrDYOlBsYeil 7H1g1bXeYhmS/oOyvpauHccJbS6dHmZAUGsGoUh6tcm1t/qxB3P13Xs47Y69QNHqgbob RSuhm/DSuNlxCGdKHQTmqlfXBCHNKiHe8p28Y6fofZZdQve0Y2ZVCVGmmHb+eN+D+hIc nJHpqcRdm+fQIVLQnVFYblAHZISsTYyR7FQAXtFUJUcAGj1dGndXYTgC6eVHZtmaAVLa DOQohwKcv0g2xSaJn7qnUjAHbgHMgtrCoNH/Lg3x4uQvvhTWlF4ZFeBqlY23epSV/NOI UWyg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1699613997; x=1700218797; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ng52OPkZn77AerY9h5LP2IbzTh0qdlf5lJORgUvbVBo=; b=l2r9XXgA+gWWxdC5t8KXb0VJiGMytf4d3dRfx2b9ihmCJl26kgNosGvBhUIzAZpxSc G82Hanp99n/rGDCG36FjGKuMaFMM7VlHW2r9BR8O+r+ocfphKlCL5VYT5VrtiRV0U/0E o3V6l3jLaWCIqVl7EpKeB7xS0wn1G7+VpZUFNCTrJijmwvtOFKa4aKCKWrM6juZF8Vx8 Mqcy8rvvJemjraUB3PS6U67erkRNSfq/a7CGYbjy6U9gQo3tb/15659LLke0mxNWpQZD yCMg8EDTfvDLzS4Yn6OQBXGGjcLmVyNai1pcP9YsTeKk19FPl+iLCv6nqmWfu+M3koin cjJw== X-Gm-Message-State: AOJu0YwugqSGXryaHLjOw4H4VnzJYoIyQadpbghc4TE1pyVoizQujadO ihOFJLmBXRZeJ8tsMqlFVde5wuvQw4mA/KmVjUOKeg== X-Google-Smtp-Source: AGHT+IGEresNeNY3Wzi7hJF7Ngv6mvkNc10+EZ/c9mvMv/Rp9m6WvoSbHlJ5NjlnCzWfSv2TMPR6Hg== X-Received: by 2002:a2e:9185:0:b0:2c6:e5f8:451e with SMTP id f5-20020a2e9185000000b002c6e5f8451emr5966751ljg.5.1699613997342; Fri, 10 Nov 2023 02:59:57 -0800 (PST) Received: from localhost.localdomain ([93.93.133.154]) by smtp.gmail.com with ESMTPSA id r15-20020a05600c35cf00b0040531f5c51asm4883156wmq.5.2023.11.10.02.59.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 10 Nov 2023 02:59:56 -0800 (PST) To: libcamera-devel@lists.libcamera.org Date: Fri, 10 Nov 2023 10:59:58 +0000 Message-Id: <20231110110002.21381-2-naush@raspberrypi.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231110110002.21381-1-naush@raspberrypi.com> References: <20231110110002.21381-1-naush@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v1 1/5] controls: Add vendor control/property support to generation scripts X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Naushir Patuck via libcamera-devel From: Naushir Patuck Reply-To: Naushir Patuck Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add support for vendor specific controls and properties to libcamera. The controls/properties are defined by a "vendor" tag in the YAML control description, for example: - RpiVendorControlExample: type: string vendor: rpi description: | Test for libcamera vendor specific controls. This will now generate a control id in the libcamera::controls::vendor::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_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.RpiVendorControlExample. The above controls example applies similarly to properties. 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. Signed-off-by: Naushir Patuck --- include/libcamera/control_ids.h.in | 2 + include/libcamera/meson.build | 13 +- include/libcamera/property_ids.h.in | 2 + src/libcamera/control_ids.cpp.in | 6 + src/libcamera/meson.build | 5 +- src/libcamera/property_ids.cpp.in | 6 + src/py/libcamera/gen-py-controls.py | 16 +- src/py/libcamera/py_controls_generated.cpp.in | 3 + .../libcamera/py_properties_generated.cpp.in | 3 + utils/gen-controls.py | 144 ++++++++++++++---- 10 files changed, 162 insertions(+), 38 deletions(-) diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in index 0718a8886f6c..c97b09a82450 100644 --- a/include/libcamera/control_ids.h.in +++ b/include/libcamera/control_ids.h.in @@ -32,6 +32,8 @@ ${draft_controls} } /* namespace draft */ +${vendor_controls} + } /* namespace controls */ } /* namespace libcamera */ diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build index a24c50d66a82..f736cca07228 100644 --- a/include/libcamera/meson.build +++ b/include/libcamera/meson.build @@ -33,19 +33,20 @@ 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_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 diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in index ff0194083af0..47c5d6bf2e28 100644 --- a/include/libcamera/property_ids.h.in +++ b/include/libcamera/property_ids.h.in @@ -31,6 +31,8 @@ ${draft_controls} extern const ControlIdMap properties; +${vendor_controls} + } /* namespace properties */ } /* namespace libcamera */ diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in index 5fb1c2c30558..d26eb930b30c 100644 --- a/src/libcamera/control_ids.cpp.in +++ b/src/libcamera/control_ids.cpp.in @@ -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,8 @@ namespace draft { ${draft_controls_def} } /* namespace draft */ + +${vendor_controls_def} #endif /** @@ -57,6 +61,8 @@ extern const ControlIdMap controls { ${controls_map} }; +${vendor_controls_map} + } /* namespace controls */ } /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index d0e26f6b4141..e49bf850b355 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -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 diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in index f917e3349766..ddbe714c3f00 100644 --- a/src/libcamera/property_ids.cpp.in +++ b/src/libcamera/property_ids.cpp.in @@ -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,8 @@ namespace draft { ${draft_controls_def} } /* namespace draft */ + +${vendor_controls_def} #endif /** @@ -53,6 +57,8 @@ extern const ControlIdMap properties { ${controls_map} }; +${vendor_controls_map} + } /* namespace properties */ } /* namespace libcamera */ diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py index 9948c41e42b1..e89de674966a 100755 --- a/src/py/libcamera/gen-py-controls.py +++ b/src/py/libcamera/gen-py-controls.py @@ -24,12 +24,24 @@ def find_common_prefix(strings): def generate_py(controls, mode): out = '' + vendors_class_def = [] + vendor_defs = [] + vendors = [] for ctrl in controls: name, ctrl = ctrl.popitem() + vendor = ctrl.get('vendor') + if vendor and vendor not in vendors: + vendors_class_def.append('class Py{}Controls\n{{\n}};\n'.format(vendor)) + vendor_defs.append('\tauto {} = py::class_(controls, \"{}\");'.format(vendor, vendor, vendor)) + vendors.append(vendor) + if ctrl.get('draft'): ns = 'libcamera::{}::draft::'.format(mode) container = 'draft' + elif vendor: + ns = 'libcamera::{}::{}::'.format(mode, vendor) + container = vendor else: ns = 'libcamera::{}::'.format(mode) container = 'controls' @@ -62,7 +74,9 @@ def generate_py(controls, mode): 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): diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in index 18fa57d948ea..ec4b55ef2011 100644 --- a/src/py/libcamera/py_controls_generated.cpp.in +++ b/src/py/libcamera/py_controls_generated.cpp.in @@ -21,10 +21,13 @@ class PyDraftControls { }; +${vendors_class_def} + void init_py_controls_generated(py::module& m) { auto controls = py::class_(m, "controls"); auto draft = py::class_(controls, "draft"); +${vendors_defs} ${controls} } diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in index e49b6e91bb83..87bc5e5d937e 100644 --- a/src/py/libcamera/py_properties_generated.cpp.in +++ b/src/py/libcamera/py_properties_generated.cpp.in @@ -21,10 +21,13 @@ class PyDraftProperties { }; +${vendors_class_def} + void init_py_properties_generated(py::module& m) { auto controls = py::class_(m, "properties"); auto draft = py::class_(controls, "draft"); +${vendors_defs} ${controls} } diff --git a/utils/gen-controls.py b/utils/gen-controls.py index 1075ae302ce1..dd55753e792c 100755 --- a/utils/gen-controls.py +++ b/utils/gen-controls.py @@ -89,6 +89,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.__data.get('vendor') if 'vendor' in self.__data else None + @property def name(self): """The control name (CamelCase)""" @@ -145,15 +150,22 @@ ${description} enum_values_start = string.Template('''extern const std::array ${name}Values = {''') enum_values_values = string.Template('''\tstatic_cast(${name}),''') - ctrls_doc = [] - ctrls_def = [] - draft_ctrls_doc = [] - draft_ctrls_def = [] - ctrls_map = [] + ctrls_doc = {} + ctrls_def = {} + ctrls_map = {} for ctrl in controls: id_name = snake_case(ctrl.name).upper() + vendor = ctrl.vendor + if vendor is None: + vendor = 'draft' if ctrl.is_draft else 'libcamera' + + if vendor not in ctrls_doc: + ctrls_doc[vendor] = [] + ctrls_def[vendor] = [] + ctrls_map[vendor] = [] + info = { 'name': ctrl.name, 'type': ctrl.type, @@ -161,11 +173,9 @@ ${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] + target_ctrls_map = ctrls_map[vendor] if ctrl.is_enum: enum_doc = [] @@ -201,41 +211,90 @@ ${description} target_doc.append(doc_template.substitute(info)) target_def.append(def_template.substitute(info)) - ctrls_map.append('\t{ ' + id_name + ', &' + ctrl.q_name + ' },') + target_ctrls_map.append('\t{ ' + id_name + ', &' + ctrl.q_name + ' },') + + vendor_ctrl_doc_sub = [] + vendor_ctrl_template = string.Template(''' +namespace ${vendor} { + +${vendor_controls_str} + +} /* namespace ${vendor} */''') + + for vendor in [v for v in ctrls_map.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_map.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])})) + + vendor_ctrl_map_sub = [] + vendor_ctrl_template = string.Template('''namespace ${vendor} { +/** + * \\brief List of all supported ${vendor} vendor controls + * + * Unless otherwise stated, all controls are bi-directional, i.e. they can be + * set through Request::controls() and returned out through Request::metadata(). + */ +extern const ControlIdMap controls { +${vendor_controls_map} +}; + +} /* namespace ${vendor} */ +''') + + for vendor in [v for v in ctrls_map.keys() if v not in ['libcamera', 'draft']]: + vendor_ctrl_map_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, + 'vendor_controls_map': '\n'.join(ctrls_map[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_map': '\n'.join(ctrls_map), + '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['libcamera'] + ctrls_map['draft']), + 'vendor_controls_doc': '\n'.join(vendor_ctrl_doc_sub), + 'vendor_controls_def': '\n'.join(vendor_ctrl_def_sub), + 'vendor_controls_map': '\n'.join(vendor_ctrl_map_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 ${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 = ctrl.vendor + if vendor is None: + vendor = 'draft' if ctrl.is_draft else 'libcamera' + + 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 +316,37 @@ 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_${vendor_def}_VENDOR_${mode} + +enum { +${vendor_enums} +}; + +extern const ControlIdMap controls; + +${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) } @@ -284,6 +368,8 @@ def main(argv): help='Input file name.') parser.add_argument('template', type=str, help='Template file name.') + parser.add_argument('--mode', type=str, required=True, choices=['controls', 'properties'], + help='Mode of operation') args = parser.parse_args(argv[1:]) data = open(args.input, 'rb').read() @@ -293,7 +379,7 @@ def main(argv): 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')