From patchwork Thu Dec 24 08:17:10 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10732 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 11E71C0F1A for ; Thu, 24 Dec 2020 08:17:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D1BB662015; Thu, 24 Dec 2020 09:17:24 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Hzb7VX+6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6F66B62010 for ; Thu, 24 Dec 2020 09:17:23 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0317FA1D; Thu, 24 Dec 2020 09:17:21 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797843; bh=xSQks8sZVbccutwl+SGwqRC5OWjWK3Fy3yuvIvmCnt4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Hzb7VX+6VQxwqsuG/mdCOKP1rCDCpgXTC6fpbtBcyEeGPV9qELP2r2VPLVKLgW+d3 UdewKvOIIVZNgY1yuGr7ZIwpnmsp8f7VRsbnwhqmdMRBiFnABWSyEGydmldMGLfl2s /jEq5ZRzVcns8HiE9AGa6pHT1EkGtVOOe8rxOkXE= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:17:10 +0900 Message-Id: <20201224081713.41742-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081713.41742-1-paul.elder@ideasonboard.com> References: <20201224081713.41742-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 1/4] tests: Add IPADataSerializer test 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Test the IPADataSerializer for controls, vectors, maps, and PODs of built-in types. Signed-off-by: Paul Elder --- Change since v6: - no longer need to initialize rpi ControlInfoMap - no longer need to pass ControlInfoMap to the ControlList serializer Changes in v5: - use ControlInfoMap serializer instead of const ControlInfoMap serializer Changes in v4: - use RPi::controls instead RPi::Controls Changes in v3: - use re-namespaced RPi::Controls New in v2 --- .../ipa_data_serializer_test.cpp | 464 ++++++++++++++++++ test/serialization/meson.build | 3 +- 2 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 test/serialization/ipa_data_serializer_test.cpp diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp new file mode 100644 index 00000000..914663ca --- /dev/null +++ b/test/serialization/ipa_data_serializer_test.cpp @@ -0,0 +1,464 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * ipa_data_serializer_test.cpp - Test serializing/deserializing with IPADataSerializer + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/ipa_data_serializer.h" +#include "libcamera/internal/ipa_manager.h" +#include "libcamera/internal/ipa_module.h" +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/thread.h" +#include "libcamera/internal/timer.h" + +#include "serialization_test.h" +#include "test.h" + +using namespace std; +using namespace libcamera; + +template +bool isVectorEqual(vector &vecA, vector &vecB) +{ + if (vecA.size() != vecB.size()) + return false; + + size_t len = vecA.size(); + for (unsigned int i = 0; i < len; i++) + if (vecA[i] != vecB[i]) + goto nequal; + + return true; + +nequal: + cerr << "lhs: { "; + for (const auto &value : vecA) + cerr << value << ", "; + cerr << "}" << endl; + + cerr << "rhs: { "; + for (const auto &value : vecB) + cerr << value << ", "; + cerr << "}" << endl; + + return false; +} + +template<> +bool isVectorEqual(vector &vecA, + vector &vecB) +{ + if (vecA.size() != vecB.size()) + return false; + + size_t len = vecA.size(); + for (unsigned int i = 0; i < len; i++) + if (!SerializationTest::equals(vecA[i], vecB[i])) + return false; + + return true; +} + +template +bool isMapEqual(map &mapA, map &mapB) +{ + if (mapA == mapB) + return true; + + cerr << "lhs: { "; + for (const auto &value : mapA) + cerr << value.first << " : " << value.second << ", "; + cerr << "}" << endl; + + cerr << "rhs: { "; + for (const auto &value : mapB) + cerr << value.first << " : " << value.second << ", "; + cerr << "}" << endl; + + return false; +} + +template +bool isMapToCimEqual(map &mapA, map &mapB) +{ + bool isEqual = true; + + auto itA = mapA.begin(); + auto itB = mapB.begin(); + while (true) { + bool endA = (itA == mapA.end()); + bool endB = (itB == mapB.end()); + + if (endA and endB) + break; + + if (!endA && (endB || itA->first < itB->first)) { + cerr << "key: " << itA->first << " not in mapB" << endl; + isEqual = false; + itA++; + continue; + } + + if (endA || itB->first < itA->first) { + cerr << "key: " << itB->first << " not in mapA" << endl; + isEqual = false; + itB++; + continue; + } + + if (!SerializationTest::equals(itA->second, itB->second)) { + cerr << "key " << itA->first + << " has different values" << endl; + isEqual = false; + } + + itA++; + itB++; + } + + return isEqual; +} + +template +bool isMapToVecEqual(map &mapA, map &mapB) +{ + if (mapA == mapB) + return true; + + cerr << "lhs: { "; + for (const auto &value : mapA) { + cerr << value.first << " : { "; + for (const auto &v : value.second) + cerr << v << ", "; + cerr << "}" << endl; + } + cerr << "}" << endl; + + cerr << "rhs: { "; + for (const auto &value : mapB) { + cerr << value.first << " : { "; + for (const auto &v : value.second) + cerr << v << ", "; + cerr << "}" << endl; + } + cerr << "}" << endl; + + return false; +} + +class IPADataSerializerTest : public CameraTest, public Test +{ +public: + IPADataSerializerTest() + : CameraTest("platform/vimc.0 Sensor B") + { + } + +protected: + int init() override + { + return status_; + } + + int run() override + { + int finalRet = TestPass; + int ret; + + ret = testControls(); + if (ret != TestPass) + finalRet = ret; + + ret = testVector(); + if (ret != TestPass) + finalRet = ret; + + ret = testMap(); + if (ret != TestPass) + finalRet = ret; + + ret = testPod(); + if (ret != TestPass) + finalRet = ret; + + return finalRet; + } + +private: + ControlList generateControlListA() + { + /* Create a control list with three controls. */ + const ControlInfoMap &infoMap = camera_->controls(); + ControlList list(infoMap); + + list.set(controls::Brightness, 0.5f); + list.set(controls::Contrast, 1.2f); + list.set(controls::Saturation, 0.2f); + + return list; + } + + int testControls() + { + ControlSerializer cs; + + const ControlInfoMap &infoMap = camera_->controls(); + ControlList list = generateControlListA(); + + vector infoMapBuf; + tie(infoMapBuf, ignore) = + IPADataSerializer::serialize(infoMap, &cs); + + vector listBuf; + tie(listBuf, ignore) = + IPADataSerializer::serialize(list, &cs); + + const ControlInfoMap infoMapOut = + IPADataSerializer::deserialize(infoMapBuf, &cs); + + ControlList listOut = IPADataSerializer::deserialize(listBuf, &cs); + + if (!SerializationTest::equals(infoMap, infoMapOut)) { + cerr << "Deserialized map doesn't match original" << endl; + return TestFail; + } + + if (!SerializationTest::equals(list, listOut)) { + cerr << "Deserialized list doesn't match original" << endl; + return TestFail; + } + + return TestPass; + } + + int testVector() + { + +#define TEST_VEC_SERDES(type, vec, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(vec, cs); \ +vector vec##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs); \ +ret = isVectorEqual(vec, vec##Out); \ +if (!ret) { \ + cerr << "Deserialized vector " << #vec << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + + ControlSerializer cs; + + /* + * We don't test FileDescriptor serdes because it dup()s, so we + * can't check for equality. + */ + vector vecUint8 = { 1, 2, 3, 4, 5, 6 }; + vector vecUint16 = { 1, 2, 3, 4, 5, 6 }; + vector vecUint32 = { 1, 2, 3, 4, 5, 6 }; + vector vecUint64 = { 1, 2, 3, 4, 5, 6 }; + vector vecInt8 = { 1, 2, 3, -4, 5, -6 }; + vector vecInt16 = { 1, 2, 3, -4, 5, -6 }; + vector vecInt32 = { 1, 2, 3, -4, 5, -6 }; + vector vecInt64 = { 1, 2, 3, -4, 5, -6 }; + vector vecFloat = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 }; + vector vecDouble = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 }; + vector vecBool = { true, true, false, false, true, false }; + vector vecString = { "foo", "bar", "baz" }; + vector vecControlInfoMap = { + camera_->controls(), + RPi::Controls, + }; + + vector buf; + vector fds; + int finalRet = TestPass; + int ret; + + TEST_VEC_SERDES(uint8_t, vecUint8, nullptr); + TEST_VEC_SERDES(uint16_t, vecUint16, nullptr); + TEST_VEC_SERDES(uint32_t, vecUint32, nullptr); + TEST_VEC_SERDES(uint64_t, vecUint64, nullptr); + TEST_VEC_SERDES(int8_t, vecInt8, nullptr); + TEST_VEC_SERDES(int16_t, vecInt16, nullptr); + TEST_VEC_SERDES(int32_t, vecInt32, nullptr); + TEST_VEC_SERDES(int64_t, vecInt64, nullptr); + TEST_VEC_SERDES(float, vecFloat, nullptr); + TEST_VEC_SERDES(double, vecDouble, nullptr); + TEST_VEC_SERDES(bool, vecBool, nullptr); + TEST_VEC_SERDES(string, vecString, nullptr); + TEST_VEC_SERDES(ControlInfoMap, vecControlInfoMap, &cs); + + return finalRet; + } + + int testMap() + { + +#define TEST_MAP_SERDES(ktype, vtype, m, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(m, cs); \ +map m##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs);\ +ret = isMapEqual(m, m##Out); \ +if (!ret) { \ + cerr << "Deserialized map " << #m << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + +#define TEST_MAP_CIM_SERDES(ktype, vtype, m, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(m, cs); \ +map m##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs);\ +ret = isMapToCimEqual(m, m##Out); \ +if (!ret) { \ + cerr << "Deserialized map " << #m << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + +#define TEST_MAP_VEC_SERDES(ktype, vtype, m, cs) \ +tie(buf, fds) = IPADataSerializer>::serialize(m, cs); \ +map m##Out = \ + IPADataSerializer>::deserialize(buf, fds, cs);\ +ret = isMapToVecEqual(m, m##Out); \ +if (!ret) { \ + cerr << "Deserialized map " << #m << " doesn't match original" << endl;\ + finalRet = TestFail; \ +} + + ControlSerializer cs; + + /* + * Realistically, only string and integral keys. + * Test simple, complex, and nested compound value. + */ + map mapUintStr = + { {101, "foo"}, {102, "bar"}, {103, "baz"} }; + map mapIntStr = + { {101, "foo"}, {-102, "bar"}, {-103, "baz"} }; + map mapStrStr = + { {"a", "foo"}, {"b", "bar"}, {"c", "baz"} }; + map mapUintCIM = + { {201, camera_->controls()}, {202, RPi::Controls} }; + map mapIntCIM = + { {201, camera_->controls()}, {-202, RPi::Controls} }; + map mapStrCIM = + { {"a", camera_->controls()}, {"b", RPi::Controls} }; + map> mapUintBVec = + { {301, { 1, 2, 3 }}, {302, {4, 5, 6}}, {303, {7, 8, 9}} }; + map> mapIntBVec = + { {301, { 1, 2, 3 }}, {-302, {4, 5, 6}}, {-303, {7, 8, 9}} }; + map> mapStrBVec = + { {"a", { 1, 2, 3 }}, {"b", {4, 5, 6}}, {"c", {7, 8, 9}} }; + + vector buf; + vector fds; + int finalRet = TestPass; + int ret; + + TEST_MAP_SERDES(uint64_t, string, mapUintStr, nullptr); + TEST_MAP_SERDES(int64_t, string, mapIntStr, nullptr); + TEST_MAP_SERDES(string, string, mapStrStr, nullptr); + TEST_MAP_CIM_SERDES(uint64_t, ControlInfoMap, mapUintCIM, &cs); + TEST_MAP_CIM_SERDES(int64_t, ControlInfoMap, mapIntCIM, &cs); + TEST_MAP_CIM_SERDES(string, ControlInfoMap, mapStrCIM, &cs); + TEST_MAP_VEC_SERDES(uint64_t, vector, mapUintBVec, nullptr); + TEST_MAP_VEC_SERDES(int64_t, vector, mapIntBVec, nullptr); + TEST_MAP_VEC_SERDES(string, vector, mapStrBVec, nullptr); + + return finalRet; + } + + int testPod() + { + +#define TEST_POD_SERDES(type, var) \ +tie(buf, fds) = IPADataSerializer::serialize(var); \ +type var##Out = \ + IPADataSerializer::deserialize(buf, fds); \ +ret = (var == var##Out); \ +if (!ret) { \ + cerr << "Deserialized " << #var << " as " << var##Out \ + << ", expected " << var << endl; \ + finalRet = TestFail; \ +} + + uint32_t u32min = numeric_limits::min(); + uint32_t u32max = numeric_limits::max(); + uint32_t u32one = 1; + int32_t i32min = numeric_limits::min(); + int32_t i32max = numeric_limits::max(); + int32_t i32one = 1; + + uint64_t u64min = numeric_limits::min(); + uint64_t u64max = numeric_limits::max(); + uint64_t u64one = 1; + int64_t i64min = numeric_limits::min(); + int64_t i64max = numeric_limits::max(); + int64_t i64one = 1; + + float flow = numeric_limits::lowest(); + float fmin = numeric_limits::min(); + float fmax = numeric_limits::max(); + float falmostOne = 1 + 1.0e-37; + double dlow = numeric_limits::lowest(); + double dmin = numeric_limits::min(); + double dmax = numeric_limits::max(); + double dalmostOne = 1 + 1.0e-307; + + bool t = true; + bool f = false; + + stringstream ss; + for (unsigned int i = 0; i < (1 << 21); i++) + ss << "0123456789"; + + string strLong = ss.str(); + string strEmpty = ""; + + vector buf; + vector fds; + int finalRet = TestPass; + int ret; + + TEST_POD_SERDES(uint32_t, u32min); + TEST_POD_SERDES(uint32_t, u32max); + TEST_POD_SERDES(uint32_t, u32one); + TEST_POD_SERDES(int32_t, i32min); + TEST_POD_SERDES(int32_t, i32max); + TEST_POD_SERDES(int32_t, i32one); + TEST_POD_SERDES(uint64_t, u64min); + TEST_POD_SERDES(uint64_t, u64max); + TEST_POD_SERDES(uint64_t, u64one); + TEST_POD_SERDES(int64_t, i64min); + TEST_POD_SERDES(int64_t, i64max); + TEST_POD_SERDES(int64_t, i64one); + TEST_POD_SERDES(float, flow); + TEST_POD_SERDES(float, fmin); + TEST_POD_SERDES(float, fmax); + TEST_POD_SERDES(float, falmostOne); + TEST_POD_SERDES(double, dlow); + TEST_POD_SERDES(double, dmin); + TEST_POD_SERDES(double, dmax); + TEST_POD_SERDES(double, dalmostOne); + TEST_POD_SERDES(bool, t); + TEST_POD_SERDES(bool, f); + TEST_POD_SERDES(string, strLong); + TEST_POD_SERDES(string, strEmpty); + + return finalRet; + } +}; + +TEST_REGISTER(IPADataSerializerTest) diff --git a/test/serialization/meson.build b/test/serialization/meson.build index a9d9cbcb..c140a31c 100644 --- a/test/serialization/meson.build +++ b/test/serialization/meson.build @@ -1,7 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 serialization_tests = [ - [ 'control_serialization', 'control_serialization.cpp' ], + [ 'control_serialization', 'control_serialization.cpp' ], + [ 'ipa_data_serializer_test', 'ipa_data_serializer_test.cpp' ], ] foreach t : serialization_tests From patchwork Thu Dec 24 08:17:12 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10733 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 BB4CCC0F1A for ; Thu, 24 Dec 2020 08:17:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8753E62005; Thu, 24 Dec 2020 09:17:29 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Odvca8ks"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A178E61D64 for ; Thu, 24 Dec 2020 09:17:27 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DCCD7DFC; Thu, 24 Dec 2020 09:17:25 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797847; bh=HKkRsR2+M4W0OyBOvJa8InfBdYGGZ30lg1Z22SDHMMw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Odvca8ks68DsmBAQ9o4wcTMcL/8ik2EOUUOvG4j9xybudzADA6DbHE1lNv8wC/+Mr TqXymFZHc/DYjq3iERj+G9ozhSx0t2XRyqIdzjpfCiuF8ud/6KPLhyS/ooUP/+jtzk TRG8c9EsiDhjPlXqTWAeHhmvYhl9559H3yTfsiPQ= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:17:12 +0900 Message-Id: <20201224081713.41742-4-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081713.41742-1-paul.elder@ideasonboard.com> References: <20201224081713.41742-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 3/4] Documentation: Add IPA writers guide 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a guide about writing IPAs. Signed-off-by: Paul Elder --- Changes in v6: - namespacing is now required - start() is now customizable - {pipeline_name}.h is no longer required - fix code block indentations Changes in v5: - fix doxygen compile errors - update example struct names from raspberry pi changes - add todo for restricting pre-start() to sync and post-start() to async Changes in v4.1: - Add section on namespacing, custom data structures, compiling, and usage of the data structures and interface - Add examples to the main ipa interface and event ipa interface sections New in v4 --- Documentation/guides/ipa.rst | 474 +++++++++++++++++++++++++++++++++++ Documentation/index.rst | 1 + Documentation/meson.build | 1 + 3 files changed, 476 insertions(+) create mode 100644 Documentation/guides/ipa.rst diff --git a/Documentation/guides/ipa.rst b/Documentation/guides/ipa.rst new file mode 100644 index 00000000..ceef5365 --- /dev/null +++ b/Documentation/guides/ipa.rst @@ -0,0 +1,474 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +IPA Writers Guide +================= + +IPA are Image Processing Algorithm modules. They provide functionality that +the pipeline handler can use for image processing. + +This guide so far only covers definition the IPA interface, and how to plumb +the connection between the pipeline handler and the IPA. + +The IPA interface and protocol +------------------------------ + +The IPA interface defines the interface between the pipeline handler and the +IPA. Specifically, it defines the functions that the IPA exposes that the +pipeline handler can call, and the Signals that the pipeline handler can +connect to to receive data from the IPA asyncrhonously. In addition, it +contains any custom data structures that the pipeline handler and IPA may +pass to each other. + +The IPA protocol refers to the agreement between the pipeline handler and the +IPA regarding the expected response(s) from the IPA for given calls to the IPA. +This protocol doesn't need to be declared anywhere in code, but it would be +useful to document it, as there may be many different IPAs for one pipeline +handler. + +The IPA interface must be defined in a mojom file. The interface includes: +- the functions that the pipeline handler can call from the IPA +- Signals in the pipeline handler that the IPA can emit +- any data structures that are to be passed between the pipeline handler and the IPA +All IPA of a given pipeline handler use the same IPA interface. The IPA +interface definition is thus likely to be written by the pipeline handler +author, based on how they imagine the pipeline handler will interact with +the IPA. + +The entire IPA interface, including the functions, Signals, and any custom +structs shall be defined in in a file named {pipeline_name}.mojom under +include/libcamera/ipa/ using the mojo interface definition language (IDL). This +will be covered in detail in the following sections. + +Namespacing +----------- + +Namespacing is required, to avoid potential collisions with libcamera types. +Use mojo's module directive for this. + +It must be the first meaningful line in the mojo data definition file, for +example (defining a raspberry pi IPA): + +.. code-block:: none + + module ipa.rpi; + +This will become the ipa::rpi namespace in C++ code. + +Data containers +--------------- + +Since the data passed between the pipeline handler and the IPA must support +serialization, any custom data containers must be defined with the mojo IDL. + +The following list of libcamera objects are supported in the interface +definition, and may be used as function parameter types or struct field types: + +- CameraSensorInfo +- ControlInfoMap +- ControlList +- FileDescriptor +- IPABuffer +- IPASettings +- IPAStream + +To use them, core.mojom must be included in the mojo data definition file: + +.. code-block:: none + + import "include/libcamera/ipa/core.mojom"; + +Other custom structs may be defined and used as well. There is no requirement +that they must be defined before usage. enums and structs are supported. + +The following is an example of a definition of an enum, for the purpose of +being used as flags: + +.. code-block:: none + + enum ConfigParameters { + ConfigLsTable = 0x01, + ConfigStaggeredWrite = 0x02, + ConfigSensor = 0x04, + ConfigDropFrames = 0x08, + }; + +The following is an example of a definition of a struct: + +.. code-block:: none + + struct ConfigInput { + uint32 op; + uint32 transform; + FileDescriptor lsTableHandle; + int32 lsTableHandleStatic = -1; + map streamConfig; + array buffers; + }; + +This example has some special things about it. First of all, it uses the +FileDescriptor data type. This type must be used to ensure that the file +descriptor that it contains is translated property across the IPC boundary +(when the IPA is in an isolated process). + +This does mean that if the file descriptor should be sent without being +translated (for example, for the IPA to tell the pipeline handler which +fd *that the pipeline handler holds* to act on), then it must be in a +regular int32 type. + +This example also illustrates that struct fields may have default values, as +is assigned to lsTableHandleStatic. This is the value that the field will +take when the struct is constructed with the default constructor. + +Arrays and maps are supported as well. They are translated to C++ vectors and +maps, respectively. The members of the arrays and maps are embedded, and cannot +be const. + +Note that nullable fields, static-length arrays, handles, and unions, which +are supported by mojo, are not supported by our code generator. + +TODO what about versioning, and numbered fields? + +The Main IPA interface +---------------------- + +The IPA interface is split in two parts, the Main IPA interface, which +describes the functions that the pipeline handler can call from the IPA, +and the Event IPA interface, which describes the Signals in the pipeline +handler that the IPA can emit. Both must be defined. This section focuses +on the Main IPA interface. + +The main interface must be named as IPA{pipeline_name}Interface. + +At minimum, the following three functions must be present (and implemented): +- init(IPASettings settings) => (int32 ret); +- start(); +- stop(); + +All three of these functions are synchronous. + +TODO Restrict pre-start to synchronous, and post-start to asynchronous + +The parameters for start() may be customized. + +A configure() method is recommended. Any ContolInfoMap instances that will be +used by the IPA must be sent to the IPA from the pipeline handler, at configure +time, for example. + +All input parameters will become const references, except for arithmetic types, +which will be passed by value. Output parameters will become pointers, unless +there is only one primitive output parameter, in which case it will become a +a regular return value. + +const is not allowed inside of arrays and maps. mojo arrays will become C++ +std::vector<>. + +By default, all methods defined in the main interface are synchronous. This +means that in the case of IPC (ie. isolated IPA), the function call will not +return until the return value or output parameters are ready. To specify an +asynchronous function, the [async] attribute can be used. Asynchronous +methods must not have any return value or output parameters, since in the +case of IPC the call needs to return immediately. + +It is also possible that the IPA will not be run in isolation. In this case, +the IPA thread will not exist until start() is called. This means that in the +case of no isolation, asynchronous calls cannot be made before start(). Since +the IPA interface must be the same regardless of isolation, the same +restriction applies to the case of isolation, and any function that will be +called before start() must be synchronous. + +In addition, any call made after start() and before stop() must be +asynchronous. The motivation for this is to avoid damaging real time +performance of the pipeline handler. If the pipeline handler wants some data +from the IPA, the IPA should return the data asynchronously via an event +(see "The Event IPA interface"). + +The following is an example of a main interface definition: + +.. code-block:: none + + interface IPARPiInterface { + init(IPASettings settings) => (int32 ret); + start() => (int32 ret); + stop(); + + configure(CameraSensorInfo sensorInfo, + map streamConfig, + map entityControls, + ConfigInput ipaConfig) + => (ConfigOutput results); + + mapBuffers(array buffers); + unmapBuffers(array ids); + + [async] signalStatReady(uint32 bufferId); + [async] signalQueueRequest(ControlList controls); + [async] signalIspPrepare(ISPConfig data); + }; + + +The first three functions are the required functions. Functions do not need to +have return values, like stop(), mapBuffers(), and unmapBuffers(). In the case +of asynchronous functions, as explained before, they *must not* have return +values. + +The Event IPA interface +----------------------- + +The event IPA interface describes the Signals in the pipeline handler that the +IPA can emit. It must be defined. If there are no event functions, then it may +be empty. These emissions are meant to notify the pipeline handler of some +event, such as reqeust data is ready, and *must not* be used to drive the +camera pipeline from the IPA. + +The event interface must be named as IPA{pipeline_name}EventInterface. + +Methods defined in the event interface are implictly asynchronous. +They thus cannot return any value. Specifying the [async] tag is not +necessary. + +Methods defined in the event interface will become Signals in the IPA +interface. The IPA can emit signals, while the pipeline handler can connect +slots to them. + +The following is an example of an event interface definition: + +.. code-block:: none + + interface IPARPiEventInterface { + statsMetadataComplete(uint32 bufferId, ControlList controls); + runIsp(uint32 bufferId); + embeddedComplete(uint32 bufferId); + setIsp(ControlList controls); + setStaggered(ControlList controls); + }; + +Compiling the IPA interface +--------------------------- + +After the IPA interface is defined in include/libcamera/ipa/{pipeline_name}.mojom, +and entry for it must be added in meson so that it can be compiled. The filename +must be added to the ipa_mojom_files object in include/libcamera/ipa/meson.build. + +For example, adding the raspberrypi.mojom file to meson: + +.. code-block:: none + + ipa_mojom_files = [ + 'raspberrypi.mojom', + ] + +This will cause the mojo data definition file to be compiled. Specifically, it +generated five files: +- a header describing the custom data structures, and the complete IPA interface +- a serializer implementing de/serialization for the custom data structures +- a proxy header describing a specialized IPA proxy +- a proxy source implementing the IPA proxy +- a proxy worker source implementing the other end of the IPA proxy + +The pipeline handler and the IPA only require the header and the proxy header. +The serializer is only used internally by the proxy. + +Using the custom data structures +-------------------------------- + +To use the custom data structures that are defined in the mojo data definition +file, the follow header must be included: + +.. code-block:: C++ + + #include + +The POD types of the structs simply become their C++ counterparts, eg. uint32 +in mojo will become uint32_t in C++. mojo map becomes C++ std::map, and mojo +array becomes C++ std::vector. All members of maps and vectors are embedded, +and are not pointers. The members cannot be const. + +All fields of structs are the name as specified in the data definition file, +with an underscore at the end. For example, the following struct as defined +in the mojo file: + +.. code-block:: none + + struct SensorConfig { + uint32 gainDelay = 1; + uint32 exposureDelay; + uint32 sensorMetadata; + }; + +Will become this in C++: + +.. code-block:: C++ + + struct SensorConfig { + uint32_t gainDelay_; + uint32_t exposureDelay_; + uint32_t sensorMetadata_; + }; + +The generated structs will also have two constructors, a constructor that +fills all fields with the default values, and a second constructor that takes +a value for every field. The default value constructor will fill in the fields +with the specified default value, if it exists. In the above example, `gainDelay_` +will be initialized to 1. If no default value is specified, then it will be +filled in as zero (or -1 for a FileDescriptor type). + +All fields and constructors/deconstructors in these generated structs are public. + +Using the IPA interface (pipeline handler) +------------------------------------------ + +The following headers are necessary to use an IPA in the pipeline handler +(with raspberrypi as an example): + +.. code-block:: C++ + + #include + #include + +The first header includes definitions of the custom data structures, and +the definition of the complete IPA interface (including both the Main and +the Event IPA interfaces). The name of the header file is comes from the name +of the mojom file, which in this case was raspberrypi.mojom. + +The second header inclues the definition of the specialized IPA proxy. It +exposes the complete IPA interface. We will see how to use it in this section. + +In the pipeline handler, we first need to construct a specialized IPA proxy. +From the point of view of the pipeline hander, this is the object that is the +IPA. + +To do so, we invoke the IPAManager: + +.. code-block:: C++ + + std::unique_ptr ipa_ = + IPAManager::createIPA(pipe_, 1, 1); + +The ipa::rpi namespace comes from the namespace that we defined in the mojo +data definition file, in the "Namespacing" section. The name of the proxy, +IPAProxyRPi, comes from the name given to the main IPA interface, +IPARPiInterface, in the "The Main IPA interface" section. + +The return value of IPAManager::createIPA shall be error-checked, to confirm +that the returned pointer is not a nullptr. + +After this, before initializing the IPA, slots should be connected to all of +the IPA's Signals, as defined in the Event IPA interface: + +.. code-block:: C++ + + ipa_->statsMetadataComplete.connect(this, &RPiCameraData::statsMetadataComplete); + ipa_->runIsp.connect(this, &RPiCameraData::runIsp); + ipa_->embeddedComplete.connect(this, &RPiCameraData::embeddedComplete); + ipa_->setIsp.connect(this, &RPiCameraData::setIsp); + ipa_->setStaggered.connect(this, &RPiCameraData::setStaggered); + +The slot functions have a function signature based on the function definition +in the Event IPA interface. All plain old data (POD) types are as-is (with +their C++ versions, eg. uint32 -> uint32_t), and all structs are const references. + +For example, for the following entry in the Event IPA interface: + +.. code-block:: none + + statsMetadataComplete(uint32 bufferId, ControlList controls); + +A function with the following function signature shall be connected to the +signal: + +.. code-block:: C++ + + statsMetadataComplete(uint32_t bufferId, const ControlList &controls); + +After connecting the slots to the signals, the IPA should be initialized +(fill in settings accordingly): + +.. code-block:: C++ + + IPASettings settings{}; + ipa_->init(settings); + +At this point, any IPA functions that were defined in the Main IPA interface +can be called as if they were regular member functions, for example: + +.. code-block:: C++ + + ipa_->start(); + ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig, &result); + ipa_->signalStatReady(RPi::BufferMask::STATS | static_cast(index)); + +Remember that any functions designated as asynchronous *must not* be called +before start(). + +TODO anything special about start() and stop() ? + +Using the IPA interface (IPA) +----------------------------- + +The following header is necessary to implement an IPA (with raspberrypi as +an example): + +.. code-block:: C++ + + #include + +This header includes definitions of the custom data structures, and +the definition of the complete IPA interface (including both the Main and +the Event IPA interfaces). The name of the header file is comes from the name +of the mojom file, which in this case was raspberrypi.mojom. + +The IPA must implement the IPA interface class that is defined in the header. +In the case of our example, that is ipa::rpi::IPARPiInterface. The ipa::rpi +namespace comes from the namespace that we defined in the mojo data definition +file, in the "Namespacing" section. The name of the interface is the same as +the name given to the Main IPA interface. + +The function signature rules are the same as for the slots in the pipeline +handler side; PODs are passed by value, and structs are passed by const +reference. For the Main IPA interface, output values are also allowed (only +for synchronous calls), so there may be output parameters as well. If the +output parameter is a single POD it will be returned by value, otherwise +(multiple PODs or struct(s)) it will be returned by output parameter pointers. + +For example, for the following function specification in the Main IPA interface +definition: + +.. code-block:: none + + configure(CameraSensorInfo sensorInfo, + uint32 exampleNumber, + map streamConfig, + map entityControls, + ConfigInput ipaConfig) + => (ConfigOutput results); + +We will need to implement a function with the following function signature: + +.. code-block:: C++ + + void configure(const CameraSensorInfo &sensorInfo, + uint32_t exampleNumber, + const std::map &streamConfig, + const std::map &entityControls, + const ipa::rpi::ConfigInput &data, + ipa::rpi::ConfigOutput *response); + +The return value is void, because the output parameter is not a single POD. +Instead, it becomes an output parameter pointer. The non-POD input parameters +become const references, and the POD input parameter is passed by value. + +At any time (though usually only in response to an IPA call), the IPA may send +data to the pipeline handler by emitting signals. These signals are defined +in the C++ IPA interface class (which is in the generated and included header). + +For example, for the following function defined in the Event IPA interface: + +.. code-block:: none + + statsMetadataComplete(uint32 bufferId, ControlList controls); + +We can emit a signal like so: + +.. code-block:: C++ + + statsMetadataComplete.emit(bufferId & RPi::BufferMask::ID, libcameraMetadata_); diff --git a/Documentation/index.rst b/Documentation/index.rst index ff697d4f..8bc8922e 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -16,4 +16,5 @@ Developer Guide Application Writer's Guide Pipeline Handler Writer's Guide + IPA Writer's guide Tracing guide diff --git a/Documentation/meson.build b/Documentation/meson.build index 26a12fcd..74381f32 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -54,6 +54,7 @@ if sphinx.found() 'docs.rst', 'index.rst', 'guides/introduction.rst', + 'guides/ipa.rst', 'guides/application-developer.rst', 'guides/pipeline-handler.rst', 'guides/tracing.rst', From patchwork Thu Dec 24 08:17:13 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10734 X-Patchwork-Delegate: paul.elder@ideasonboard.com 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 4F7A0C0F1A for ; Thu, 24 Dec 2020 08:17:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 21ED66201B; Thu, 24 Dec 2020 09:17:31 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WG9PNFiI"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9BCAA6200B for ; Thu, 24 Dec 2020 09:17:29 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1D382A1D; Thu, 24 Dec 2020 09:17:27 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1608797849; bh=b9ygJ9OpgevuQvBulgPAi2JMp9C6q7Vxmb599AWtjOc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WG9PNFiI+xf89w+BiT+8t6pstk3lmBw5sKFgRVzjzG5GS6M+/RaMuReSWiLzlGnSr ZpOY+NJ+t9S+VJZYar03GuX8dAFChy7i21DC8MxYN9/p9pvrm3gxU+i//D4SI8f2WV lFBXdpf+omvht/yb4njgsSQeuUT+u1StsE0ufuEM= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 24 Dec 2020 17:17:13 +0900 Message-Id: <20201224081713.41742-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201224081713.41742-1-paul.elder@ideasonboard.com> References: <20201224081713.41742-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 4/4] tests: Test IPA serializer generation 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a test to confirm that serializer and header generation works properly for mojom definition files, and that the serializer works properly. Signed-off-by: Paul Elder --- Changes in v6: - use namespacing in the mojom file, and in the test - add the enum to the test mojom file, as vimc.h no longer exists Changes in v5: - add dummy event to event interface New in v4 --- .../generated_serializer_test.cpp | 128 ++++++++++++++++++ .../generated_serializer/meson.build | 49 +++++++ .../generated_serializer/vimc.mojom | 33 +++++ test/serialization/meson.build | 2 + 4 files changed, 212 insertions(+) create mode 100644 test/serialization/generated_serializer/generated_serializer_test.cpp create mode 100644 test/serialization/generated_serializer/meson.build create mode 100644 test/serialization/generated_serializer/vimc.mojom diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp new file mode 100644 index 00000000..2f499c24 --- /dev/null +++ b/test/serialization/generated_serializer/generated_serializer_test.cpp @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * generated_serializer_test.cpp - Test generated serializer + */ + +#include +#include +#include + +#include "test.h" + +#include "vimc_test_ipa_interface.h" +#include "vimc_test_ipa_serializer.h" + +using namespace std; +using namespace libcamera; + +class IPAGeneratedSerializerTest : public Test +{ +protected: + int init() override + { + return TestPass; + } + + int run() override + { + +#define TEST_FIELD_EQUALITY(struct1, struct2, field) \ +if (struct1.field != struct2.field) { \ + cerr << #field << " field incorrect: expected \"" \ + << t.field << "\", got \"" << u.field << "\"" << endl;\ + return TestFail; \ +} + + ipa::test::TestStruct t, u; + + t.m = { + { "a", "z" }, + { "b", "z" }, + { "c", "z" }, + { "d", "z" }, + { "e", "z" }, + }; + + t.a = { "a", "b", "c", "d", "e" }; + + t.s1 = "hello world"; + t.s2 = "goodbye"; + t.s3 = "lorem ipsum"; + t.i = 58527; + + vector serialized; + + tie(serialized, ignore) = + IPADataSerializer::serialize(t); + + u = IPADataSerializer::deserialize(serialized); + + if (!equals(t.m, u.m)) + return TestFail; + + if (!equals(t.a, u.a)) + return TestFail; + + TEST_FIELD_EQUALITY(t, u, s1); + TEST_FIELD_EQUALITY(t, u, s2); + TEST_FIELD_EQUALITY(t, u, s3); + TEST_FIELD_EQUALITY(t, u, i); + + return TestPass; + } + +private: + bool equals(const map &lhs, const map &rhs) + { + bool eq = lhs.size() == rhs.size() && + equal(lhs.begin(), lhs.end(), rhs.begin(), + [](auto &a, auto &b) { return a.first == b.first && + a.second == b.second; }); + + if (eq) + return true; + + cerr << "lhs:" << endl; + for (const auto &pair : lhs) + cerr << "- " << pair.first << ": " + << pair.second << endl; + + cerr << "rhs:" << endl; + for (const auto &pair : rhs) + cerr << "- " << pair.first << ": " + << pair.second << endl; + + return false; + } + + bool equals(const vector &lhs, const vector &rhs) + { + bool eq = lhs.size() == rhs.size(); + + if (!eq) { + cerr << "sizes not equal" << endl; + return false; + } + + for (unsigned int i = 0; i < lhs.size(); i++) + if (lhs[i] != rhs[i]) + eq = false; + + if (eq) + return true; + + cerr << "lhs:" << endl; + for (const auto &str : lhs) + cerr << "- " << str << endl; + + cerr << "rhs:" << endl; + for (const auto &str : rhs) + cerr << "- " << str << endl; + + return false; + } +}; + +TEST_REGISTER(IPAGeneratedSerializerTest) diff --git a/test/serialization/generated_serializer/meson.build b/test/serialization/generated_serializer/meson.build new file mode 100644 index 00000000..68bcf23d --- /dev/null +++ b/test/serialization/generated_serializer/meson.build @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: CC0-1.0 + +# vimc.mojom-module +mojom = custom_target('vimc_test_mojom_module', + input : 'vimc.mojom', + output : 'vimc.mojom-module', + command : [ + mojom_parser, + '--output-root', meson.build_root(), + '--input-root', meson.source_root(), + '--mojoms', '@INPUT@' + ]) + +# vimc_test_ipa_interface.h +header = custom_target('vimc_test_ipa_interface_h', + input : mojom, + output : 'vimc_test_ipa_interface.h', + depends : mojom_templates, + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_header', + '--libcamera_output_path=@OUTPUT@', + './' +'@INPUT@' + ]) + +# vimc_test_ipa_serializer.h +serializer = custom_target('vimc_test_ipa_serializer_h', + input : mojom, + output : 'vimc_test_ipa_serializer.h', + depends : mojom_templates, + command : [ + mojom_generator, 'generate', + '-g', 'libcamera', + '--bytecode_path', mojom_templates_dir, + '--libcamera_generate_serializer', + '--libcamera_output_path=@OUTPUT@', + './' +'@INPUT@' + ]) + +exe = executable('generated_serializer_test', + ['generated_serializer_test.cpp', header, serializer], + dependencies : libcamera_dep, + link_with : test_libraries, + include_directories : test_includes_internal) + +test('generated_serializer_test', exe, + suite : 'generated_serializer', is_parallel : false) diff --git a/test/serialization/generated_serializer/vimc.mojom b/test/serialization/generated_serializer/vimc.mojom new file mode 100644 index 00000000..2fd973e9 --- /dev/null +++ b/test/serialization/generated_serializer/vimc.mojom @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +module ipa.test; + +enum IPAOperationCode { + IPAOperationNone, + IPAOperationInit, + IPAOperationStart, + IPAOperationStop, +}; + +struct IPASettings {}; + +struct TestStruct { + map m; + array a; + string s1; + string s2; + int32 i; + string s3; +}; + +interface IPAVimcInterface { + init(IPASettings settings) => (int32 ret); + start() => (int32 ret); + stop(); + + test(TestStruct s); +}; + +interface IPAVimcEventInterface { + dummyEvent(uint32 val); +}; diff --git a/test/serialization/meson.build b/test/serialization/meson.build index c140a31c..57726b12 100644 --- a/test/serialization/meson.build +++ b/test/serialization/meson.build @@ -1,5 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 +subdir('generated_serializer') + serialization_tests = [ [ 'control_serialization', 'control_serialization.cpp' ], [ 'ipa_data_serializer_test', 'ipa_data_serializer_test.cpp' ],