{"id":2422,"url":"https://patchwork.libcamera.org/api/1.1/patches/2422/?format=json","web_url":"https://patchwork.libcamera.org/patch/2422/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20191211145327.58633-1-jacopo@jmondi.org>","date":"2019-12-11T14:53:26","name":"[libcamera-devel,RFC,1/2] WIP libcamera: controls: Support compound controls","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"5c8503728e39e89777dc567a63a5aaae157cebbf","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/1.1/people/3/?format=json","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"delegate":{"id":15,"url":"https://patchwork.libcamera.org/api/1.1/users/15/?format=json","username":"jmondi","first_name":"Jacopo","last_name":"Mondi","email":"jacopo@jmondi.org"},"mbox":"https://patchwork.libcamera.org/patch/2422/mbox/","series":[{"id":587,"url":"https://patchwork.libcamera.org/api/1.1/series/587/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=587","date":"2019-12-11T14:53:26","name":"[libcamera-devel,RFC,1/2] WIP libcamera: controls: Support compound controls","version":1,"mbox":"https://patchwork.libcamera.org/series/587/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/2422/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/2422/checks/","tags":{},"headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net\n\t[217.70.183.199])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 591D0600F5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 11 Dec 2019 15:51:19 +0100 (CET)","from uno.lan (93-34-114-233.ip49.fastwebnet.it [93.34.114.233])\n\t(Authenticated sender: jacopo@jmondi.org)\n\tby relay9-d.mail.gandi.net (Postfix) with ESMTPSA id C1727FF809;\n\tWed, 11 Dec 2019 14:51:18 +0000 (UTC)"],"X-Originating-IP":"93.34.114.233","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"libcamera-devel@lists.libcamera.org","Date":"Wed, 11 Dec 2019 15:53:26 +0100","Message-Id":"<20191211145327.58633-1-jacopo@jmondi.org>","X-Mailer":"git-send-email 2.24.0","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [RFC 1/2] WIP libcamera: controls: Support\n\tcompound controls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 11 Dec 2019 14:51:19 -0000"},"content":"A compound control transports an array of data values. Add\nsupport to compound controls to the ControlValue class.\n\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\n---\n include/libcamera/controls.h         |  50 +++-\n include/libcamera/span.h             | 111 +++++++++\n src/libcamera/control_ids.in         |   0\n src/libcamera/control_ids.yaml       |   4 +\n src/libcamera/control_serializer.cpp |  15 +-\n src/libcamera/controls.cpp           | 344 +++++++++++++++++++++++++--\n src/libcamera/pipeline/vimc.cpp      |   7 +\n test/controls/compound_controls.cpp  |  94 ++++++++\n test/controls/meson.build            |   1 +\n 9 files changed, 596 insertions(+), 30 deletions(-)\n create mode 100644 include/libcamera/span.h\n create mode 100644 src/libcamera/control_ids.in\n create mode 100644 test/controls/compound_controls.cpp","diff":"diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h\nindex 458b84e8fa8c..b9f8d4db0bea 100644\n--- a/include/libcamera/controls.h\n+++ b/include/libcamera/controls.h\n@@ -11,6 +11,8 @@\n #include <string>\n #include <unordered_map>\n \n+#include <libcamera/span.h>\n+\n namespace libcamera {\n \n class ControlValidator;\n@@ -18,8 +20,15 @@ class ControlValidator;\n enum ControlType {\n \tControlTypeNone,\n \tControlTypeBool,\n+\tControlTypeInteger8,\n \tControlTypeInteger32,\n \tControlTypeInteger64,\n+\tControlTypeFloat,\n+\tControlTypeCompoundBool,\n+\tControlTypeCompoundInt8,\n+\tControlTypeCompoundInt32,\n+\tControlTypeCompoundInt64,\n+\tControlTypeCompoundFloat,\n };\n \n class ControlValue\n@@ -27,16 +36,28 @@ class ControlValue\n public:\n \tControlValue();\n \tControlValue(bool value);\n+\tControlValue(int8_t value);\n \tControlValue(int32_t value);\n \tControlValue(int64_t value);\n+\tControlValue(float value);\n+\tControlValue(Span<bool> &values);\n+\tControlValue(Span<int8_t> &values);\n+\tControlValue(Span<int32_t> &values);\n+\tControlValue(Span<int64_t> &values);\n+\tControlValue(Span<float> &values);\n+\t~ControlValue();\n \n \tControlType type() const { return type_; }\n \tbool isNone() const { return type_ == ControlTypeNone; }\n+\tstd::size_t numElements() const { return numElements_; }\n \n \ttemplate<typename T>\n-\tconst T &get() const;\n+\tconst Span<T> get() const;\n+\n \ttemplate<typename T>\n \tvoid set(const T &value);\n+\ttemplate<typename T>\n+\tvoid set(const Span<T> &values);\n \n \tstd::string toString() const;\n \n@@ -51,9 +72,23 @@ private:\n \n \tunion {\n \t\tbool bool_;\n+\t\tint8_t integer8_;\n \t\tint32_t integer32_;\n \t\tint64_t integer64_;\n+\t\tfloat float_;\n \t};\n+\n+\tbool *pbool_;\n+\tint8_t *p8_;\n+\tint32_t *p32_;\n+\tint64_t *p64_;\n+\tfloat *pfloat_;\n+\n+\tstd::size_t numElements_;\n+\n+\tvoid release();\n+\tbool compareElement(const ControlValue &other, unsigned int i) const;\n+\tstd::string elemToString(unsigned int i) const;\n };\n \n class ControlId\n@@ -182,6 +217,7 @@ public:\n \n private:\n \tvoid generateIdmap();\n+\tbool matchRangeType(enum ControlType type1, enum ControlType type2);\n \n \tControlIdMap idmap_;\n };\n@@ -212,7 +248,7 @@ public:\n \tbool contains(unsigned int id) const;\n \n \ttemplate<typename T>\n-\tconst T &get(const Control<T> &ctrl) const\n+\tconst Span<T> get(const Control<T> &ctrl) const\n \t{\n \t\tconst ControlValue *val = find(ctrl.id());\n \t\tif (!val) {\n@@ -233,6 +269,16 @@ public:\n \t\tval->set<T>(value);\n \t}\n \n+\ttemplate<typename T>\n+\tvoid set(const Control<T> &ctrl, const Span<T> &values)\n+\t{\n+\t\tControlValue *val = find(ctrl.id());\n+\t\tif (!val)\n+\t\t\treturn;\n+\n+\t\tval->set<T>(values);\n+\t}\n+\n \tconst ControlValue &get(unsigned int id) const;\n \tvoid set(unsigned int id, const ControlValue &value);\n \ndiff --git a/include/libcamera/span.h b/include/libcamera/span.h\nnew file mode 100644\nindex 000000000000..8ff0ebc7c6d2\n--- /dev/null\n+++ b/include/libcamera/span.h\n@@ -0,0 +1,111 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * span.h - C++20 std::span<> implementation for C++11\n+ */\n+\n+#ifndef __LIBCAMERA_SPAN_H__\n+#define __LIBCAMERA_SPAN_H__\n+\n+#include <iostream>\n+\n+#include <array>\n+#include <cstddef>\n+#include <cstdint>\n+\n+namespace libcamera {\n+\n+template <typename T>\n+class Span\n+{\n+private:\n+\tusing iterator = T *;\n+\tusing const_iterator = const T *;\n+\tusing reverse_iterator = std::reverse_iterator<iterator>;\n+\tusing const_reverse_iterator = std::reverse_iterator<const_iterator>;\n+\n+\tclass Storage\n+\t{\n+\tpublic:\n+\t\tStorage(T *ptr, std::size_t size)\n+\t\t\t: ptr_(ptr), size_(size)\n+\t\t{\n+\t\t}\n+\n+\t\tT *ptr() const { return ptr_; }\n+\t\tstd::size_t size() const { return size_; }\n+\n+\tprivate:\n+\t\tT *ptr_;\n+\t\tstd::size_t size_;\n+\t};\n+\n+public:\n+\tSpan(T &v)\n+\t\t: storage_(&v, 1)\n+\t{\n+\t}\n+\n+\tSpan(const T &v)\n+\t\t: storage_(const_cast<T *>(&v), 1)\n+\t{\n+\t}\n+\n+\tSpan(T *v, std::size_t s)\n+\t\t: storage_(v, s)\n+\t{\n+\t}\n+\n+\tSpan(const T *v, std::size_t s)\n+\t\t: storage_(const_cast<T *>(v), s)\n+\t{\n+\t}\n+\n+\tSpan(std::initializer_list<T> list)\n+\t\t: storage_(const_cast<T *>(list.begin()), list.size())\n+\t{\n+\t}\n+\n+\tSpan(const Span &other) = default;\n+\n+\tSpan &operator=(const Span &other) = default;\n+\toperator T() const { return *data(); }\n+\tT &operator[](unsigned int index) const\n+\t{\n+\t\tif (index >= size())\n+\t\t\treturn *(end() - 1);\n+\t\treturn *(data() + index);\n+\t}\n+\n+\tT *data() const { return storage_.ptr(); }\n+\tstd::size_t size() const { return storage_.size(); }\n+\n+\tconstexpr iterator begin() const { return data(); }\n+\tconstexpr iterator end() const { return data() + size(); }\n+\tconstexpr iterator cbegin() const { return begin(); }\n+\tconstexpr iterator cend() const { return end(); }\n+\tconstexpr reverse_iterator rbegin() const\n+\t{\n+\t\treturn reverse_iterator(end());\n+\t}\n+\tconstexpr reverse_iterator rend() const\n+\t{\n+\t\treturn reverse_iterator(begin());\n+\t}\n+\tconstexpr const_reverse_iterator crbegin() const\n+\t{\n+\t\treturn const_reverse_iterator(end());\n+\t}\n+\tconstexpr const_reverse_iterator crend() const\n+\t{\n+\t\treturn const_reverse_iterator(begin());\n+\t}\n+\n+private:\n+\tStorage storage_;\n+};\n+\n+}; /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_SPAN_H__ */\ndiff --git a/src/libcamera/control_ids.in b/src/libcamera/control_ids.in\nnew file mode 100644\nindex 000000000000..e69de29bb2d1\ndiff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\nindex 4befec746a59..b72e81dae060 100644\n--- a/src/libcamera/control_ids.yaml\n+++ b/src/libcamera/control_ids.yaml\n@@ -50,4 +50,8 @@ controls:\n       type: int32_t\n       description: Specify a fixed gain parameter\n \n+  - CompoundControl:\n+      type: int8_t\n+      description: A fictional compound control\n+\n ...\ndiff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp\nindex b787655e6769..2140d3a5dd3f 100644\n--- a/src/libcamera/control_serializer.cpp\n+++ b/src/libcamera/control_serializer.cpp\n@@ -30,10 +30,17 @@ LOG_DEFINE_CATEGORY(Serializer)\n namespace {\n \n static constexpr size_t ControlValueSize[] = {\n-\t[ControlTypeNone]\t= 1,\n-\t[ControlTypeBool]\t= sizeof(bool),\n-\t[ControlTypeInteger32]\t= sizeof(int32_t),\n-\t[ControlTypeInteger64]\t= sizeof(int64_t),\n+\t[ControlTypeNone]\t\t= 1,\n+\t[ControlTypeBool]\t\t= sizeof(bool),\n+\t[ControlTypeInteger8]\t\t= sizeof(int8_t),\n+\t[ControlTypeInteger32]\t\t= sizeof(int32_t),\n+\t[ControlTypeInteger64]\t\t= sizeof(int64_t),\n+\t[ControlTypeFloat]\t\t= sizeof(float),\n+\t[ControlTypeCompoundBool]\t= sizeof(bool *),\n+\t[ControlTypeCompoundInt8]\t= sizeof(int8_t *),\n+\t[ControlTypeCompoundInt32]\t= sizeof(int32_t *),\n+\t[ControlTypeCompoundInt64]\t= sizeof(int64_t *),\n+\t[ControlTypeCompoundFloat]\t= sizeof(float *),\n };\n \n } /* namespace */\ndiff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\nindex 1678d3cba4f3..64d99f7669b6 100644\n--- a/src/libcamera/controls.cpp\n+++ b/src/libcamera/controls.cpp\n@@ -9,7 +9,9 @@\n \n #include <iomanip>\n #include <sstream>\n-#include <string>\n+#include <string.h>\n+\n+#include <libcamera/span.h>\n \n #include \"control_validator.h\"\n #include \"log.h\"\n@@ -69,7 +71,8 @@ LOG_DEFINE_CATEGORY(Controls)\n  * \\brief Construct an empty ControlValue.\n  */\n ControlValue::ControlValue()\n-\t: type_(ControlTypeNone)\n+\t: type_(ControlTypeNone), pbool_(nullptr), p8_(nullptr), p32_(nullptr),\n+\t  p64_(nullptr), pfloat_(nullptr), numElements_(0)\n {\n }\n \n@@ -78,7 +81,15 @@ ControlValue::ControlValue()\n  * \\param[in] value Boolean value to store\n  */\n ControlValue::ControlValue(bool value)\n-\t: type_(ControlTypeBool), bool_(value)\n+\t: type_(ControlTypeBool), bool_(value), pbool_(nullptr), p8_(nullptr),\n+\t  p32_(nullptr), p64_(nullptr), pfloat_(nullptr), numElements_(1)\n+{\n+}\n+\n+ControlValue::ControlValue(int8_t value)\n+\t: type_(ControlTypeInteger8), integer8_(value), pbool_(nullptr),\n+\t  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(1)\n {\n }\n \n@@ -87,7 +98,9 @@ ControlValue::ControlValue(bool value)\n  * \\param[in] value Integer value to store\n  */\n ControlValue::ControlValue(int32_t value)\n-\t: type_(ControlTypeInteger32), integer32_(value)\n+\t: type_(ControlTypeInteger32), integer32_(value), pbool_(nullptr),\n+\t  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(1)\n {\n }\n \n@@ -96,8 +109,87 @@ ControlValue::ControlValue(int32_t value)\n  * \\param[in] value Integer value to store\n  */\n ControlValue::ControlValue(int64_t value)\n-\t: type_(ControlTypeInteger64), integer64_(value)\n+\t: type_(ControlTypeInteger64), integer64_(value), pbool_(nullptr),\n+\t  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(1)\n+{\n+}\n+\n+ControlValue::ControlValue(float value)\n+\t: type_(ControlTypeInteger64), float_(value), pbool_(nullptr),\n+\t  p8_(nullptr), p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(1)\n+{\n+}\n+\n+ControlValue::ControlValue(Span<bool> &values)\n+\t: type_(ControlTypeCompoundBool), pbool_(nullptr), p8_(nullptr),\n+\t  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(values.size())\n+{\n+\tpbool_ = new bool[numElements_];\n+\tmemcpy(pbool_, values.data(), sizeof(bool) * numElements_);\n+}\n+\n+ControlValue::ControlValue(Span<int8_t> &values)\n+\t: type_(ControlTypeCompoundInt8),pbool_(nullptr), p8_(nullptr),\n+\t  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(values.size())\n+{\n+\tp8_ = new int8_t[numElements_];\n+\tmemcpy(p8_, values.data(), sizeof(int8_t) * numElements_);\n+}\n+\n+ControlValue::ControlValue(Span<int32_t> &values)\n+\t: type_(ControlTypeCompoundInt32), pbool_(nullptr), p8_(nullptr),\n+\t  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(values.size())\n+{\n+\tp32_ = new int32_t[numElements_];\n+\tmemcpy(p32_, values.data(), sizeof(int32_t) * numElements_);\n+}\n+\n+ControlValue::ControlValue(Span<int64_t> &values)\n+\t: type_(ControlTypeCompoundInt64), pbool_(nullptr), p8_(nullptr),\n+\t  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(values.size())\n {\n+\tp64_ = new int64_t[numElements_];\n+\tmemcpy(p64_, values.data(), sizeof(int64_t) * numElements_);\n+}\n+\n+ControlValue::ControlValue(Span<float> &values)\n+\t: type_(ControlTypeCompoundFloat), pbool_(nullptr), p8_(nullptr),\n+\t  p32_(nullptr), p64_(nullptr), pfloat_(nullptr),\n+\t  numElements_(values.size())\n+{\n+\tpfloat_ = new float[numElements_];\n+\tmemcpy(pfloat_, values.data(), sizeof(float) * numElements_);\n+}\n+\n+void ControlValue::release()\n+{\n+\tif (pbool_)\n+\t\tdelete[] pbool_;\n+\tif (p8_)\n+\t\tdelete[] p8_;\n+\tif (p32_)\n+\t\tdelete[] p32_;\n+\tif (p64_)\n+\t\tdelete[] p64_;\n+\tif (pfloat_)\n+\t\tdelete[] pfloat_;\n+\n+\tpbool_ = nullptr;\n+\tp8_ = nullptr;\n+\tp32_ = nullptr;\n+\tp64_ = nullptr;\n+\tpfloat_ = nullptr;\n+}\n+\n+ControlValue::~ControlValue()\n+{\n+\trelease();\n }\n \n /**\n@@ -130,69 +222,225 @@ ControlValue::ControlValue(int64_t value)\n \n #ifndef __DOXYGEN__\n template<>\n-const bool &ControlValue::get<bool>() const\n+const Span<bool> ControlValue::get<bool>() const\n {\n-\tASSERT(type_ == ControlTypeBool);\n+\tASSERT(type_ == ControlTypeBool || type_ == ControlTypeCompoundBool);\n \n-\treturn bool_;\n+\tconst bool *p = pbool_ ? pbool_ : &bool_;\n+\t/*\n+\t * Explicitly create a Span<bool> instance, otherwise the compiler\n+\t * tries to match the \"{ p, numElements_}\" construct used in other get()\n+\t * operation specialization with the initializer_list constructor of\n+\t * class Span (clang++ 8.0.1)\n+\t */\n+\treturn Span<bool>(p, static_cast<std::size_t>(numElements_));\n }\n \n template<>\n-const int32_t &ControlValue::get<int32_t>() const\n+const Span<int8_t> ControlValue::get<int8_t>() const\n {\n-\tASSERT(type_ == ControlTypeInteger32 || type_ == ControlTypeInteger64);\n+\tASSERT(type_ == ControlTypeInteger8 ||\n+\t       type_ == ControlTypeCompoundInt8);\n \n-\treturn integer32_;\n+\tconst int8_t *p = p8_ ? p8_ : &integer8_;\n+\treturn { p, static_cast<std::size_t>(numElements_) };\n }\n \n template<>\n-const int64_t &ControlValue::get<int64_t>() const\n+const Span<int32_t> ControlValue::get<int32_t>() const\n {\n-\tASSERT(type_ == ControlTypeInteger32 || type_ == ControlTypeInteger64);\n+\tASSERT(type_ == ControlTypeInteger32 ||\n+\t       type_ == ControlTypeInteger64 ||\n+\t       type_ == ControlTypeCompoundInt32 ||\n+\t       type_ == ControlTypeCompoundInt64);\n \n-\treturn integer64_;\n+\tconst int32_t *p = p32_ ? p32_ : &integer32_;\n+\treturn { p, numElements_ };\n+}\n+\n+template<>\n+const Span<int64_t> ControlValue::get<int64_t>() const\n+{\n+\tASSERT(type_ == ControlTypeInteger32 ||\n+\t       type_ == ControlTypeInteger64 ||\n+\t       type_ == ControlTypeCompoundInt32 ||\n+\t       type_ == ControlTypeCompoundInt64);\n+\n+\tconst int64_t *p = p64_ ? p64_ : &integer64_;\n+\treturn { p, numElements_ };\n+}\n+\n+template<>\n+const Span<float> ControlValue::get<float>() const\n+{\n+\tASSERT(type_ == ControlTypeFloat || type_ == ControlTypeCompoundFloat);\n+\n+\tconst float *p = pfloat_ ? pfloat_ : &float_;\n+\treturn { p, numElements_ };\n }\n \n template<>\n void ControlValue::set<bool>(const bool &value)\n {\n+\trelease();\n+\n \ttype_ = ControlTypeBool;\n \tbool_ = value;\n+\tnumElements_ = 1;\n }\n \n template<>\n void ControlValue::set<int32_t>(const int32_t &value)\n {\n+\trelease();\n+\n \ttype_ = ControlTypeInteger32;\n \tinteger32_ = value;\n+\tnumElements_ = 1;\n }\n \n template<>\n void ControlValue::set<int64_t>(const int64_t &value)\n {\n+\trelease();\n+\n \ttype_ = ControlTypeInteger64;\n \tinteger64_ = value;\n+\tnumElements_ = 1;\n }\n+\n+template<>\n+void ControlValue::set<bool>(const Span<bool> &values)\n+{\n+\trelease();\n+\n+\ttype_ = ControlTypeCompoundBool;\n+\tnumElements_ = values.size();\n+\n+\tpbool_ = new bool[numElements_];\n+\tmemcpy(pbool_, values.data(), sizeof(bool) * numElements_);\n+}\n+\n+template<>\n+void ControlValue::set<int8_t>(const Span<int8_t> &values)\n+{\n+\trelease();\n+\n+\ttype_ = ControlTypeCompoundInt8;\n+\tnumElements_ = values.size();\n+\n+\tp8_ = new int8_t[numElements_];\n+\tmemcpy(p8_, values.data(), sizeof(int8_t) * numElements_);\n+}\n+\n+template<>\n+void ControlValue::set<int32_t>(const Span<int32_t> &values)\n+{\n+\trelease();\n+\n+\ttype_ = ControlTypeCompoundInt32;\n+\tnumElements_ = values.size();\n+\n+\tp32_ = new int32_t[numElements_];\n+\tmemcpy(p32_, values.data(), sizeof(int32_t) * numElements_);\n+}\n+\n+template<>\n+void ControlValue::set<int64_t>(const Span<int64_t> &values)\n+{\n+\trelease();\n+\n+\ttype_ = ControlTypeCompoundInt64;\n+\tnumElements_ = values.size();\n+\n+\tp64_ = new int64_t[numElements_];\n+\tmemcpy(p64_, values.data(), sizeof(int64_t) * numElements_);\n+}\n+\n+template<>\n+void ControlValue::set<float>(const Span<float> &values)\n+{\n+\trelease();\n+\n+\ttype_ = ControlTypeCompoundFloat;\n+\tnumElements_ = values.size();\n+\n+\tpfloat_ = new float[numElements_];\n+\tmemcpy(pfloat_, values.data(), sizeof(float) * numElements_);\n+}\n+\n #endif /* __DOXYGEN__ */\n \n /**\n  * \\brief Assemble and return a string describing the value\n  * \\return A string describing the ControlValue\n  */\n-std::string ControlValue::toString() const\n+std::string ControlValue::elemToString(unsigned int i) const\n {\n \tswitch (type_) {\n-\tcase ControlTypeNone:\n-\t\treturn \"<None>\";\n \tcase ControlTypeBool:\n-\t\treturn bool_ ? \"True\" : \"False\";\n+\t\treturn bool_ ? \"True \" : \"False \";\n+\tcase ControlTypeInteger8:\n+\t\treturn std::to_string(integer8_);\n \tcase ControlTypeInteger32:\n \t\treturn std::to_string(integer32_);\n \tcase ControlTypeInteger64:\n \t\treturn std::to_string(integer64_);\n+\tcase ControlTypeFloat:\n+\t\treturn std::to_string(float_);\n+\tcase ControlTypeCompoundBool:\n+\t\treturn pbool_[i] ? \"True \" : \"False \";\n+\tcase ControlTypeCompoundInt8:\n+\t\treturn std::to_string(p8_[i]) + \" \";\n+\tcase ControlTypeCompoundInt32:\n+\t\treturn std::to_string(p32_[i]) + \" \";\n+\tcase ControlTypeCompoundInt64:\n+\t\treturn std::to_string(p64_[i]) + \" \";\n+\tcase ControlTypeCompoundFloat:\n+\t\treturn std::to_string(pfloat_[i]) + \" \";\n+\tdefault:\n+\t\treturn \"<None>\";\n \t}\n+}\n+\n+std::string ControlValue::toString() const\n+{\n+\tif (ControlTypeNone)\n+\t\treturn \"<ValueType Error>\";\n+\n+\tstd::string str;\n+\tfor (unsigned int i = 0; i < numElements_; ++i)\n+\t\tstr += elemToString(i);\n+\n+\treturn str;\n+}\n \n-\treturn \"<ValueType Error>\";\n+bool ControlValue::compareElement(const ControlValue &other, unsigned int i) const\n+{\n+\tswitch (type_) {\n+\tcase ControlTypeBool:\n+\t\treturn bool_ == other.bool_;\n+\tcase ControlTypeInteger8:\n+\t\treturn integer8_ == other.integer8_;\n+\tcase ControlTypeInteger32:\n+\t\treturn integer32_ == other.integer32_;\n+\tcase ControlTypeInteger64:\n+\t\treturn integer64_ == other.integer64_;\n+\tcase ControlTypeFloat:\n+\t\treturn float_ == other.float_;\n+\tcase ControlTypeCompoundBool:\n+\t\treturn pbool_[i] == other.pbool_[i];\n+\tcase ControlTypeCompoundInt8:\n+\t\treturn p8_[i] == other.p8_[i];\n+\tcase ControlTypeCompoundInt32:\n+\t\treturn p32_[i] == other.p32_[i];\n+\tcase ControlTypeCompoundInt64:\n+\t\treturn p64_[i] == other.p64_[i];\n+\tcase ControlTypeCompoundFloat:\n+\t\treturn pfloat_[i] == other.pfloat_[i];\n+\tdefault:\n+\t\treturn false;\n+\t}\n }\n \n /**\n@@ -204,13 +452,27 @@ bool ControlValue::operator==(const ControlValue &other) const\n \tif (type_ != other.type_)\n \t\treturn false;\n \n+\tif (numElements_ != other.numElements())\n+\t\treturn false;\n+\n \tswitch (type_) {\n \tcase ControlTypeBool:\n-\t\treturn bool_ == other.bool_;\n+\tcase ControlTypeInteger8:\n \tcase ControlTypeInteger32:\n-\t\treturn integer32_ == other.integer32_;\n \tcase ControlTypeInteger64:\n-\t\treturn integer64_ == other.integer64_;\n+\tcase ControlTypeFloat:\n+\t\treturn compareElement(other, 0);\n+\tcase ControlTypeCompoundBool:\n+\tcase ControlTypeCompoundInt8:\n+\tcase ControlTypeCompoundInt32:\n+\tcase ControlTypeCompoundInt64:\n+\tcase ControlTypeCompoundFloat:\n+\t\tfor (unsigned int i = 0; i < numElements_; ++i) {\n+\t\t\tif (!compareElement(other, i))\n+\t\t\t\treturn false;\n+\t\t}\n+\n+\t\treturn true;\n \tdefault:\n \t\treturn false;\n \t}\n@@ -330,6 +592,12 @@ Control<bool>::Control(unsigned int id, const char *name)\n {\n }\n \n+template<>\n+Control<int8_t>::Control(unsigned int id, const char *name)\n+\t: ControlId(id, name, ControlTypeInteger8)\n+{\n+}\n+\n template<>\n Control<int32_t>::Control(unsigned int id, const char *name)\n \t: ControlId(id, name, ControlTypeInteger32)\n@@ -341,6 +609,12 @@ Control<int64_t>::Control(unsigned int id, const char *name)\n \t: ControlId(id, name, ControlTypeInteger64)\n {\n }\n+\n+template<>\n+Control<float>::Control(unsigned int id, const char *name)\n+\t: ControlId(id, name, ControlTypeFloat)\n+{\n+}\n #endif /* __DOXYGEN__ */\n \n /**\n@@ -583,15 +857,37 @@ ControlInfoMap::const_iterator ControlInfoMap::find(unsigned int id) const\n  * \\return The ControlId map\n  */\n \n+bool ControlInfoMap::matchRangeType(enum ControlType type1, enum ControlType type2)\n+{\n+\tif (type1 == type2)\n+\t\treturn true;\n+\n+\tswitch (type1) {\n+\tcase ControlTypeCompoundInt8:\n+\t\treturn type2 == ControlTypeInteger8;\n+\tcase ControlTypeCompoundInt32:\n+\t\treturn type2 == ControlTypeInteger32;\n+\tcase ControlTypeCompoundInt64:\n+\t\treturn type2 == ControlTypeInteger64;\n+\tcase ControlTypeCompoundFloat:\n+\t\treturn type2 == ControlTypeFloat;\n+\tdefault:\n+\t\treturn false;\n+\t}\n+}\n+\n void ControlInfoMap::generateIdmap()\n {\n \tidmap_.clear();\n \n \tfor (const auto &ctrl : *this) {\n-\t\tif (ctrl.first->type() != ctrl.second.min().type()) {\n+\t\tif (!matchRangeType(ctrl.first->type(),\n+\t\t\t\t    ctrl.second.min().type())) {\n \t\t\tLOG(Controls, Error)\n \t\t\t\t<< \"Control \" << utils::hex(ctrl.first->id())\n-\t\t\t\t<< \" type and range type mismatch\";\n+\t\t\t\t<< \" type and range type mismatch: \"\n+\t\t\t\t<< ctrl.first->type() << \" - \"\n+\t\t\t\t<< ctrl.second.min().type();\n \t\t\tidmap_.clear();\n \t\t\tclear();\n \t\t\treturn;\ndiff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\nindex f043cf55889e..2bfab35314c4 100644\n--- a/src/libcamera/pipeline/vimc.cpp\n+++ b/src/libcamera/pipeline/vimc.cpp\n@@ -453,6 +453,13 @@ int VimcCameraData::init(MediaDevice *media)\n \t\t\t      std::forward_as_tuple(range));\n \t}\n \n+\t/* Register a compound control. */\n+\tctrls.emplace(std::piecewise_construct,\n+\t\t      std::forward_as_tuple(&controls::CompoundControl),\n+\t\t      std::forward_as_tuple(0, 100));\n+\n+\n+\n \tcontrolInfo_ = std::move(ctrls);\n \n \t/* Initialize the camera properties. */\ndiff --git a/test/controls/compound_controls.cpp b/test/controls/compound_controls.cpp\nnew file mode 100644\nindex 000000000000..de00573df141\n--- /dev/null\n+++ b/test/controls/compound_controls.cpp\n@@ -0,0 +1,94 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * compound_controls.cpp - CompoundControls test\n+ */\n+\n+#include <iostream>\n+#include <vector>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/camera_manager.h>\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+#include <libcamera/span.h>\n+\n+#include \"camera_controls.h\"\n+\n+#include \"camera_test.h\"\n+#include \"test.h\"\n+\n+using namespace std;\n+using namespace libcamera;\n+\n+class CompoundControlsTest : public CameraTest, public Test\n+{\n+public:\n+\tCompoundControlsTest()\n+\t\t: CameraTest(\"VIMC Sensor B\")\n+\t{\n+\t}\n+\n+protected:\n+\tint init() override\n+\t{\n+\t\treturn status_;\n+\t}\n+\n+\tint run() override\n+\t{\n+\t\tCameraControlValidator validator(camera_.get());\n+\t\tControlList list(controls::controls, &validator);\n+\n+\t\t/*\n+\t\t * Span\n+\t\t *\n+\t\t * A Span can be initialized with an initializer list\n+\t\t * and sequentially or random accessed\n+\t\t */\n+\t\tSpan<int32_t> span = { 1, 2, 3 };\n+\t\tfor (uint32_t i : span)\n+\t\t\tcout << i << endl;\n+\t\tcout << span[0] << endl;\n+\n+\t\t/*\n+\t\t * Compound Controls\n+\t\t *\n+\t\t * As of now, all Controls are now 'compounds' when set with a\n+\t\t * Span<> of values.\n+\t\t *\n+\t\t * We need to define how to establish that a Control is actually\n+\t\t * a compound or supports a single value.\n+\t\t */\n+\t\tlist.set(controls::Brightness, {0, 125, 255});\n+\t\tSpan<int32_t> iSpan = list.get(controls::Brightness);\n+\n+\t\tcout << iSpan.size() << endl;\n+\t\tfor (uint32_t i : iSpan)\n+\t\t\tcout << i << endl;\n+\n+\t\t/*\n+\t\t * But they can still be accessed and operated with a single\n+\t\t * value.\n+\t\t */\n+\t\tlist.set(controls::Brightness, 112);\n+\t\tcout << list.get(controls::Brightness) << endl;\n+\n+\t\t/*\n+\t\t * Or set with a Span and accessed by value. The first item\n+\t\t * is returned.\n+\t\t */\n+\t\tlist.set(controls::Brightness, {50, 125});\n+\t\tcout << list.get(controls::Brightness) << endl;\n+\n+\t\t/* The other way around works as well. */\n+\t\tlist.set(controls::Brightness, 133);\n+\t\tSpan<int32_t> s = list.get(controls::Brightness);\n+\t\tcout << s << endl;\n+\n+\t\treturn TestPass;\n+\t}\n+};\n+\n+TEST_REGISTER(CompoundControlsTest)\ndiff --git a/test/controls/meson.build b/test/controls/meson.build\nindex f0850df28c8a..f4752dcff33b 100644\n--- a/test/controls/meson.build\n+++ b/test/controls/meson.build\n@@ -3,6 +3,7 @@ control_tests = [\n     [ 'control_list',   'control_list.cpp' ],\n     [ 'control_range',  'control_range.cpp' ],\n     [ 'control_value',  'control_value.cpp' ],\n+    [ 'compound_controls', 'compound_controls.cpp'],\n ]\n \n foreach t : control_tests\n","prefixes":["libcamera-devel","RFC","1/2"]}