diff options
Diffstat (limited to 'users/flokli')
61 files changed, 7170 insertions, 0 deletions
diff --git a/users/flokli/OWNERS b/users/flokli/OWNERS new file mode 100644 index 000000000000..a70f5eefffd5 --- /dev/null +++ b/users/flokli/OWNERS @@ -0,0 +1,3 @@ +set noparent + +flokli diff --git a/users/flokli/archeology/OWNERS b/users/flokli/archeology/OWNERS new file mode 100644 index 000000000000..b9bc074a8020 --- /dev/null +++ b/users/flokli/archeology/OWNERS @@ -0,0 +1 @@ +edef diff --git a/users/flokli/archeology/README.md b/users/flokli/archeology/README.md new file mode 100644 index 000000000000..e4cd9b84b0d8 --- /dev/null +++ b/users/flokli/archeology/README.md @@ -0,0 +1,5 @@ +# archeology + +This directory contains various scripts and helpers used for nix-archeology tasks. + +It's used from some of the archeology instances, as well as standalone. diff --git a/users/flokli/archeology/default.nix b/users/flokli/archeology/default.nix new file mode 100644 index 000000000000..d642399cbe68 --- /dev/null +++ b/users/flokli/archeology/default.nix @@ -0,0 +1,51 @@ +{ depot, pkgs, ... }: + +let + clickhouseConfigAWS = builtins.toFile "clickhouse-local.xml" '' + <clickhouse> + <s3> + <use_environment_credentials>true</use_environment_credentials> + </s3> + </clickhouse> + ''; + # clickhouse has a very odd AWS config concept. + # Configure it to be a bit more sane. + clickhoseLocalFixedAWS = pkgs.runCommand "clickhouse-local-fixed" + { + nativeBuildInputs = [ pkgs.makeWrapper ]; + } '' + mkdir -p $out/bin + makeWrapper ${pkgs.clickhouse}/bin/clickhouse-local $out/bin/clickhouse-local \ + --append-flags "-C ${clickhouseConfigAWS}" + ''; +in + +depot.nix.readTree.drvTargets { + inherit clickhoseLocalFixedAWS; + parse-bucket-logs = pkgs.runCommand "archeology-parse-bucket-logs" + { + nativeBuildInputs = [ pkgs.makeWrapper ]; + } '' + mkdir -p $out/bin + makeWrapper ${(pkgs.writers.writeRust "parse-bucket-logs-unwrapped" {} ./parse_bucket_logs.rs)} $out/bin/archeology-parse-bucket-logs \ + --prefix PATH : ${pkgs.lib.makeBinPath [ clickhoseLocalFixedAWS ]} + ''; + + shell = pkgs.mkShell { + name = "archeology-shell"; + packages = with pkgs; [ awscli2 clickhoseLocalFixedAWS rust-analyzer rustc rustfmt ]; + + AWS_PROFILE = "sso"; + AWS_CONFIG_FILE = pkgs.writeText "aws-config" '' + [sso-session nixos] + sso_region = eu-north-1 + sso_start_url = https://nixos.awsapps.com/start + sso_registration_scopes = sso:account:access + + [profile "sso"] + sso_session = nixos + sso_account_id = 080433136561 + sso_role_name = archeologist + ''; + }; +} diff --git a/users/flokli/archeology/parse_bucket_logs.rs b/users/flokli/archeology/parse_bucket_logs.rs new file mode 100644 index 000000000000..3ab2e133b34c --- /dev/null +++ b/users/flokli/archeology/parse_bucket_logs.rs @@ -0,0 +1,42 @@ +use std::env; +use std::process::Command; +use std::process::ExitCode; + +fn main() -> ExitCode { + let args: Vec<String> = env::args().collect(); + if args.len() != 3 { + eprintln!("needs two args, input s3 url (glob) and output pq file"); + return ExitCode::FAILURE; + } + + let input_files = &args[1]; + let output_file = &args[2]; + + let mut cmd = Command::new("clickhouse-local"); + cmd.arg("--progress") + .arg("-q") + .arg(format!(r#"SELECT + key, + toInt64(nullif(http_status, '-')) AS http_status, + toInt64(nullif(object_size_str, '-')) AS object_size, + toInt64(nullif(bytes_sent_str, '-')) AS bytes_sent, + nullif(user_agent, '-') AS user_agent, + operation, + nullif(requester, '-') AS requester, + parseDateTime(timestamp_str, '%d/%b/%Y:%k:%i:%s %z') AS timestamp + FROM s3( + '{}', + 'Regexp', + 'owner String , bucket String, timestamp_str String, remote_ip String, requester LowCardinality(String), request_id String, operation LowCardinality(String), key String, request_uri String, http_status String, error_code String, bytes_sent_str String, object_size_str String, total_time String, turn_around_time String, referer String, user_agent String, version_id String, host_id String, signature_version String, cipher_suite String, authentication_type String, host_header String, tls_version String, access_point_arn String, acl_required String' + ) + ORDER BY timestamp ASC + SETTINGS + format_regexp_skip_unmatched = 1, + format_regexp = '(\\S+) (\\S+) \\[(.*)\\] (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) ((?:\\S+ \\S+ \\S+)|\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) ("\\S+") (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+).*', + output_format_parquet_compression_method = 'zstd' + INTO OUTFILE '{}' FORMAT Parquet"#, input_files, output_file)); + + cmd.status().expect("clickhouse-local failed"); + + ExitCode::SUCCESS +} diff --git a/users/flokli/archivist/OWNERS b/users/flokli/archivist/OWNERS new file mode 100644 index 000000000000..b9bc074a8020 --- /dev/null +++ b/users/flokli/archivist/OWNERS @@ -0,0 +1 @@ +edef diff --git a/users/flokli/archivist/default.nix b/users/flokli/archivist/default.nix new file mode 100644 index 000000000000..ef49c46db2f6 --- /dev/null +++ b/users/flokli/archivist/default.nix @@ -0,0 +1,28 @@ +{ depot +, pkgs +, ... +}: +depot.nix.readTree.drvTargets { + shell = pkgs.mkShell { + name = "archivist-shell"; + packages = with pkgs; [ awscli2 ]; + + AWS_PROFILE = "archivist"; + AWS_CONFIG_FILE = pkgs.writeText "aws-config" '' + [sso-session nixos] + sso_region = eu-north-1 + sso_start_url = https://nixos.awsapps.com/start + sso_registration_scopes = sso:account:access + + [profile "archivist"] + sso_session = nixos + sso_account_id = 286553126452 + sso_role_name = AWSAdministratorAccess + + [profile "archeologist"] + sso_session = nixos + sso_account_id = 080433136561 + sso_role_name = archeologist + ''; + }; +} diff --git a/users/flokli/ipu6-softisp/README.md b/users/flokli/ipu6-softisp/README.md new file mode 100644 index 000000000000..9c09e9a158e4 --- /dev/null +++ b/users/flokli/ipu6-softisp/README.md @@ -0,0 +1,26 @@ +# ipu6-softisp + +This code adds support for the ipu6 webcams via libcamera, based on the work in +https://copr.fedorainfracloud.org/coprs/jwrdegoede/ipu6-softisp/. + +It's supposed to be included in your NixOS configuration imports, and will: + + - Add some patches to your kernel, which should apply on 6.7.x + - Add the `ipu6-camera-bins` firmware (still needed) + - Enable some kernel config options + - Add an udev rule so libcamera can do DMABUF things + - Override `services.pipewire.package` and + `services.pipewire.wireplumber.package` to use a pipewire built with a libcamera + with support for this webcam. + +Please make sure you don't have any of the `hardware.ipu6` options still +enabled, as they use the closed-source userspace stack and will conflict. + +The testing instructions from +https://copr.fedorainfracloud.org/coprs/jwrdegoede/ipu6-softisp/ still apply. + +`qcam` can be found in `libcamera-qcam` (pending on +https://github.com/NixOS/nixpkgs/pull/284964 to trickle into master). + +Thanks to Hans de Goede for helping me bringing this up, as well as to +puckipedia for sorting out some pipewire-related confusion. diff --git a/users/flokli/ipu6-softisp/config.nix b/users/flokli/ipu6-softisp/config.nix new file mode 100644 index 000000000000..9232d5bc9ee5 --- /dev/null +++ b/users/flokli/ipu6-softisp/config.nix @@ -0,0 +1,94 @@ +{ pkgs, lib, ... }: + +let + libcamera = pkgs.libcamera.overrideAttrs (old: { + # This is a mix of #281755 (bump pipewire to 0.2.0), + # and the additional ipu6-softisp patches. + version = "0.2.0"; + src = pkgs.fetchgit { + url = "https://git.libcamera.org/libcamera/libcamera.git"; + rev = "v0.2.0"; + hash = "sha256-x0Im9m9MoACJhQKorMI34YQ+/bd62NdAPc2nWwaJAvM="; + }; + + mesonFlags = old.mesonFlags or [ ] ++ [ + "-Dpipelines=simple/simple,ipu3,uvcvideo" + "-Dipas=simple/simple,ipu3" + ]; + + # Explicitly clear list of patches, as #281755 did. + # This is + # https://copr-dist-git.fedorainfracloud.org/cgit/jwrdegoede/ipu6-softisp/libcamera.git/plain/libcamera-0.2.0-softisp.patch?h=f39&id=60e6b3d5e366a360a75942073dc0d642e4900982, + # but manually piped to git and back, as some renames were not processed properly. + patches = [ + ./libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch + ./libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch + ./libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch + ./libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch + ./libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch + ./libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch + ./libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch + ./libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch + ./libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch + ./libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch + ./libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch + ./libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch + ./libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch + ./libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch + ./libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch + ./libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch + ./libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch + ./libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch + ./libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch + ./libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch + ./libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch + ./libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch + ./libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch + ./libcamera/0024-ov01a1s-HACK.patch + ./libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch + ]; + }); + + # compat with libcamera 0.2 + pipewire' = (pkgs.pipewire.overrideAttrs (old: { + patches = old.patches or [ ] ++ [ + (pkgs.fetchpatch { + # https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/1750 + name = "pipewire-spa-libcamera-use-cameraconfiguration-orientation-pr1750.patch"; + url = "https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/1750.patch "; + hash = "sha256-Ugg913KZDKELnYLwpDEgYh92YPxccw61l6kAJulBbIA="; + }) + ]; + })).override { + inherit libcamera; + }; + + wireplumber' = (pkgs.wireplumber.override { + pipewire = pipewire'; + }); +in +{ + hardware.firmware = [ pkgs.ipu6-camera-bins ]; + + boot.kernelPatches = [{ + name = "linux-kernel-test.patch"; + patch = pkgs.fetchurl { + url = "https://copr-dist-git.fedorainfracloud.org/cgit/jwrdegoede/ipu6-softisp/kernel.git/plain/linux-kernel-test.patch?h=f39&id=0ed76891b2fc08579d08bedb9294a41840007299"; + hash = "sha256-8hKP4nltGlkzr8iOgsIUT9Tt5i+x4kdLmw/+lFeNoGQ="; + }; + extraStructuredConfig = { + # needed for /dev/dma_heap + DMABUF_HEAPS_CMA = lib.kernel.yes; + DMABUF_HEAPS_SYSTEM = lib.kernel.yes; + DMABUF_HEAPS = lib.kernel.yes; + }; + }]; + + + services.udev.extraRules = '' + KERNEL=="system", SUBSYSTEM=="dma_heap", TAG+="uaccess" + ''; + + services.pipewire.package = pipewire'; + services.pipewire.wireplumber.package = wireplumber'; +} diff --git a/users/flokli/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch b/users/flokli/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch new file mode 100644 index 000000000000..7a71ed1db7a4 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch @@ -0,0 +1,43 @@ +From aa818f7b749122f916be1ced48d1a3a2b3aeb47e Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Tue, 2 Jan 2024 23:47:20 +0300 +Subject: [PATCH 01/25] libcamera: pipeline: simple: fix size adjustment in + validate() + +SimpleCameraConfiguration::validate() adjusts the configuration +of its streams (if the size is not in the outputSizes) to +the captureSize. But the captureSize itself can be not in the +outputSizes, and then the adjusted configuration won't be +valid resulting in camera configuration failure. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + src/libcamera/pipeline/simple/simple.cpp | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp +index 911051b2..4d0e7255 100644 +--- a/src/libcamera/pipeline/simple/simple.cpp ++++ b/src/libcamera/pipeline/simple/simple.cpp +@@ -997,10 +997,13 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() + } + + if (!pipeConfig_->outputSizes.contains(cfg.size)) { ++ Size adjustedSize = pipeConfig_->captureSize; ++ if (!pipeConfig_->outputSizes.contains(adjustedSize)) ++ adjustedSize = pipeConfig_->outputSizes.max; + LOG(SimplePipeline, Debug) + << "Adjusting size from " << cfg.size +- << " to " << pipeConfig_->captureSize; +- cfg.size = pipeConfig_->captureSize; ++ << " to " << adjustedSize; ++ cfg.size = adjustedSize; + status = Adjusted; + } + +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch b/users/flokli/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch new file mode 100644 index 000000000000..85a27ba8e226 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch @@ -0,0 +1,194 @@ +From ca3bcfde49f069a85f7860f61d8c3bd196f97139 Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Tue, 26 Dec 2023 16:55:08 +0300 +Subject: [PATCH 02/25] libcamera: internal: Move dma_heaps.[h,cpp] to common + directories + +DmaHeap class is useful outside the RPi pipeline handler too. + +Move dma_heaps.h and dma_heaps.cpp to common directories. Update +the build files and RPi vc4 pipeline handler accordingly. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + .../libcamera/internal}/dma_heaps.h | 4 ---- + include/libcamera/internal/meson.build | 1 + + .../{pipeline/rpi/vc4 => }/dma_heaps.cpp | 18 +++++++----------- + src/libcamera/meson.build | 1 + + src/libcamera/pipeline/rpi/vc4/meson.build | 1 - + src/libcamera/pipeline/rpi/vc4/vc4.cpp | 5 ++--- + 6 files changed, 11 insertions(+), 19 deletions(-) + rename {src/libcamera/pipeline/rpi/vc4 => include/libcamera/internal}/dma_heaps.h (92%) + rename src/libcamera/{pipeline/rpi/vc4 => }/dma_heaps.cpp (83%) + +diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h b/include/libcamera/internal/dma_heaps.h +similarity index 92% +rename from src/libcamera/pipeline/rpi/vc4/dma_heaps.h +rename to include/libcamera/internal/dma_heaps.h +index 0a4a8d86..cff8f140 100644 +--- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h ++++ b/include/libcamera/internal/dma_heaps.h +@@ -13,8 +13,6 @@ + + namespace libcamera { + +-namespace RPi { +- + class DmaHeap + { + public: +@@ -27,6 +25,4 @@ private: + UniqueFD dmaHeapHandle_; + }; + +-} /* namespace RPi */ +- + } /* namespace libcamera */ +diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build +index 7f1f3440..33eb0fb3 100644 +--- a/include/libcamera/internal/meson.build ++++ b/include/libcamera/internal/meson.build +@@ -25,6 +25,7 @@ libcamera_internal_headers = files([ + 'device_enumerator.h', + 'device_enumerator_sysfs.h', + 'device_enumerator_udev.h', ++ 'dma_heaps.h', + 'formats.h', + 'framebuffer.h', + 'ipa_manager.h', +diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp +similarity index 83% +rename from src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp +rename to src/libcamera/dma_heaps.cpp +index 317b1fc1..7444d9c2 100644 +--- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp ++++ b/src/libcamera/dma_heaps.cpp +@@ -5,8 +5,6 @@ + * dma_heaps.h - Helper class for dma-heap allocations. + */ + +-#include "dma_heaps.h" +- + #include <array> + #include <fcntl.h> + #include <linux/dma-buf.h> +@@ -16,6 +14,8 @@ + + #include <libcamera/base/log.h> + ++#include "libcamera/internal/dma_heaps.h" ++ + /* + * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma + * to only have to worry about importing. +@@ -30,9 +30,7 @@ static constexpr std::array<const char *, 2> heapNames = { + + namespace libcamera { + +-LOG_DECLARE_CATEGORY(RPI) +- +-namespace RPi { ++LOG_DEFINE_CATEGORY(DmaHeap) + + DmaHeap::DmaHeap() + { +@@ -40,7 +38,7 @@ DmaHeap::DmaHeap() + int ret = ::open(name, O_RDWR | O_CLOEXEC, 0); + if (ret < 0) { + ret = errno; +- LOG(RPI, Debug) << "Failed to open " << name << ": " ++ LOG(DmaHeap, Debug) << "Failed to open " << name << ": " + << strerror(ret); + continue; + } +@@ -50,7 +48,7 @@ DmaHeap::DmaHeap() + } + + if (!dmaHeapHandle_.isValid()) +- LOG(RPI, Error) << "Could not open any dmaHeap device"; ++ LOG(DmaHeap, Error) << "Could not open any dmaHeap device"; + } + + DmaHeap::~DmaHeap() = default; +@@ -69,7 +67,7 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size) + + ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc); + if (ret < 0) { +- LOG(RPI, Error) << "dmaHeap allocation failure for " ++ LOG(DmaHeap, Error) << "dmaHeap allocation failure for " + << name; + return {}; + } +@@ -77,7 +75,7 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size) + UniqueFD allocFd(alloc.fd); + ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name); + if (ret < 0) { +- LOG(RPI, Error) << "dmaHeap naming failure for " ++ LOG(DmaHeap, Error) << "dmaHeap naming failure for " + << name; + return {}; + } +@@ -85,6 +83,4 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size) + return allocFd; + } + +-} /* namespace RPi */ +- + } /* namespace libcamera */ +diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build +index 45f63e93..3c5e43df 100644 +--- a/src/libcamera/meson.build ++++ b/src/libcamera/meson.build +@@ -17,6 +17,7 @@ libcamera_sources = files([ + 'delayed_controls.cpp', + 'device_enumerator.cpp', + 'device_enumerator_sysfs.cpp', ++ 'dma_heaps.cpp', + 'fence.cpp', + 'formats.cpp', + 'framebuffer.cpp', +diff --git a/src/libcamera/pipeline/rpi/vc4/meson.build b/src/libcamera/pipeline/rpi/vc4/meson.build +index cdb049c5..386e2296 100644 +--- a/src/libcamera/pipeline/rpi/vc4/meson.build ++++ b/src/libcamera/pipeline/rpi/vc4/meson.build +@@ -1,7 +1,6 @@ + # SPDX-License-Identifier: CC0-1.0 + + libcamera_sources += files([ +- 'dma_heaps.cpp', + 'vc4.cpp', + ]) + +diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp +index 26102ea7..3a42e75e 100644 +--- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp ++++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp +@@ -12,12 +12,11 @@ + #include <libcamera/formats.h> + + #include "libcamera/internal/device_enumerator.h" ++#include "libcamera/internal/dma_heaps.h" + + #include "../common/pipeline_base.h" + #include "../common/rpi_stream.h" + +-#include "dma_heaps.h" +- + using namespace std::chrono_literals; + + namespace libcamera { +@@ -87,7 +86,7 @@ public: + RPi::Device<Isp, 4> isp_; + + /* DMAHEAP allocation helper. */ +- RPi::DmaHeap dmaHeap_; ++ DmaHeap dmaHeap_; + SharedFD lsTable_; + + struct Config { +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch b/users/flokli/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch new file mode 100644 index 000000000000..de819244871a --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch @@ -0,0 +1,121 @@ +From 6d5f3b0b54df4ff66079675a4c1f0f0b76778e22 Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Wed, 10 Jan 2024 23:51:25 +0300 +Subject: [PATCH 03/25] libcamera: dma_heaps: extend DmaHeap class to support + system heap + +Add an argument to the constructor to specify dma heaps type(s) +to use. Can be DmaHeapFlag::Cma and/or DmaHeapFlag::System. +By default DmaHeapFlag::Cma is used. If both DmaHeapFlag::Cma and +DmaHeapFlag::System are set, CMA heap is tried first. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + include/libcamera/internal/dma_heaps.h | 12 +++++++- + src/libcamera/dma_heaps.cpp | 39 +++++++++++++++----------- + 2 files changed, 34 insertions(+), 17 deletions(-) + +diff --git a/include/libcamera/internal/dma_heaps.h b/include/libcamera/internal/dma_heaps.h +index cff8f140..22aa1007 100644 +--- a/include/libcamera/internal/dma_heaps.h ++++ b/include/libcamera/internal/dma_heaps.h +@@ -9,6 +9,7 @@ + + #include <stddef.h> + ++#include <libcamera/base/flags.h> + #include <libcamera/base/unique_fd.h> + + namespace libcamera { +@@ -16,7 +17,14 @@ namespace libcamera { + class DmaHeap + { + public: +- DmaHeap(); ++ enum class DmaHeapFlag { ++ Cma = (1 << 0), ++ System = (1 << 1), ++ }; ++ ++ using DmaHeapFlags = Flags<DmaHeapFlag>; ++ ++ DmaHeap(DmaHeapFlags flags = DmaHeapFlag::Cma); + ~DmaHeap(); + bool isValid() const { return dmaHeapHandle_.isValid(); } + UniqueFD alloc(const char *name, std::size_t size); +@@ -25,4 +33,6 @@ private: + UniqueFD dmaHeapHandle_; + }; + ++LIBCAMERA_FLAGS_ENABLE_OPERATORS(DmaHeap::DmaHeapFlag) ++ + } /* namespace libcamera */ +diff --git a/src/libcamera/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp +index 7444d9c2..177de31b 100644 +--- a/src/libcamera/dma_heaps.cpp ++++ b/src/libcamera/dma_heaps.cpp +@@ -16,6 +16,8 @@ + + #include "libcamera/internal/dma_heaps.h" + ++namespace libcamera { ++ + /* + * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma + * to only have to worry about importing. +@@ -23,28 +25,33 @@ + * Annoyingly, should the cma heap size be specified on the kernel command line + * instead of DT, the heap gets named "reserved" instead. + */ +-static constexpr std::array<const char *, 2> heapNames = { +- "/dev/dma_heap/linux,cma", +- "/dev/dma_heap/reserved" ++static constexpr std::array<std::pair<DmaHeap::DmaHeapFlag, const char *>, 3> heapNames = { ++ /* CMA heap names first */ ++ std::make_pair(DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/linux,cma"), ++ std::make_pair(DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/reserved"), ++ std::make_pair(DmaHeap::DmaHeapFlag::System, "/dev/dma_heap/system") + }; + +-namespace libcamera { +- + LOG_DEFINE_CATEGORY(DmaHeap) + +-DmaHeap::DmaHeap() ++DmaHeap::DmaHeap(DmaHeapFlags flags) + { +- for (const char *name : heapNames) { +- int ret = ::open(name, O_RDWR | O_CLOEXEC, 0); +- if (ret < 0) { +- ret = errno; +- LOG(DmaHeap, Debug) << "Failed to open " << name << ": " +- << strerror(ret); +- continue; +- } ++ int ret; + +- dmaHeapHandle_ = UniqueFD(ret); +- break; ++ for (const auto &name : heapNames) { ++ if (flags & name.first) { ++ ret = ::open(name.second, O_RDWR | O_CLOEXEC, 0); ++ if (ret < 0) { ++ ret = errno; ++ LOG(DmaHeap, Debug) << "Failed to open " << name.second << ": " ++ << strerror(ret); ++ continue; ++ } ++ ++ LOG(DmaHeap, Debug) << "Using " << name.second; ++ dmaHeapHandle_ = UniqueFD(ret); ++ break; ++ } + } + + if (!dmaHeapHandle_.isValid()) +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch b/users/flokli/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch new file mode 100644 index 000000000000..022270723a4e --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch @@ -0,0 +1,57 @@ +From 006a4a31a6803e92ec67f48b66da2cdff8b2f6ab Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Sun, 29 Oct 2023 15:56:48 +0300 +Subject: [PATCH 04/25] libcamera: internal: Move SharedMemObject class to a + common directory + +Move SharedMemObject class out of RPi namespace and put it into +include/libcamera/internal so that everyone could use it. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + include/libcamera/internal/meson.build | 1 + + .../common => include/libcamera/internal}/shared_mem_object.h | 4 ---- + 2 files changed, 1 insertion(+), 4 deletions(-) + rename {src/libcamera/pipeline/rpi/common => include/libcamera/internal}/shared_mem_object.h (98%) + +diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build +index 33eb0fb3..5807dfd9 100644 +--- a/include/libcamera/internal/meson.build ++++ b/include/libcamera/internal/meson.build +@@ -39,6 +39,7 @@ libcamera_internal_headers = files([ + 'process.h', + 'pub_key.h', + 'request.h', ++ 'shared_mem_object.h', + 'source_paths.h', + 'sysfs.h', + 'v4l2_device.h', +diff --git a/src/libcamera/pipeline/rpi/common/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h +similarity index 98% +rename from src/libcamera/pipeline/rpi/common/shared_mem_object.h +rename to include/libcamera/internal/shared_mem_object.h +index aa56c220..bfb639ee 100644 +--- a/src/libcamera/pipeline/rpi/common/shared_mem_object.h ++++ b/include/libcamera/internal/shared_mem_object.h +@@ -19,8 +19,6 @@ + + namespace libcamera { + +-namespace RPi { +- + template<class T> + class SharedMemObject + { +@@ -123,6 +121,4 @@ private: + T *obj_; + }; + +-} /* namespace RPi */ +- + } /* namespace libcamera */ +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch b/users/flokli/ipu6-softisp/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch new file mode 100644 index 000000000000..a20d27059db0 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch @@ -0,0 +1,139 @@ +From cb9ff82efd82af8ae26b2aca4183928c74f7ef34 Mon Sep 17 00:00:00 2001 +From: Dennis Bonke <admin@dennisbonke.com> +Date: Wed, 20 Dec 2023 16:22:29 +0100 +Subject: [PATCH 05/25] libcamera: internal: Document the SharedMemObject class + +Document the SharedMemObject class. + +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + .../libcamera/internal/shared_mem_object.h | 53 +++++++++++++++++++ + 1 file changed, 53 insertions(+) + +diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h +index bfb639ee..e862ce48 100644 +--- a/include/libcamera/internal/shared_mem_object.h ++++ b/include/libcamera/internal/shared_mem_object.h +@@ -19,10 +19,20 @@ + + namespace libcamera { + ++/** ++ * \class SharedMemObject ++ * \brief Helper class for shared memory allocations. ++ * ++ * Takes a template T which is used to indicate the ++ * data type of the object stored. ++ */ + template<class T> + class SharedMemObject + { + public: ++ /** ++ * \brief The size of the object that is going to be stored here. ++ */ + static constexpr std::size_t SIZE = sizeof(T); + + SharedMemObject() +@@ -30,6 +40,11 @@ public: + { + } + ++ /** ++ * \brief Contstructor for the SharedMemObject. ++ * \param[in] name The requested name. ++ * \param[in] args Any additional args. ++ */ + template<class... Args> + SharedMemObject(const std::string &name, Args &&...args) + : name_(name), obj_(nullptr) +@@ -57,6 +72,10 @@ public: + obj_ = new (mem) T(std::forward<Args>(args)...); + } + ++ /** ++ * \brief Move constructor for SharedMemObject. ++ * \param[in] rhs The object to move. ++ */ + SharedMemObject(SharedMemObject<T> &&rhs) + { + this->name_ = std::move(rhs.name_); +@@ -76,6 +95,10 @@ public: + /* Make SharedMemObject non-copyable for now. */ + LIBCAMERA_DISABLE_COPY(SharedMemObject) + ++ /** ++ * \brief Operator= for SharedMemObject. ++ * \param[in] rhs The SharedMemObject object to take the data from. ++ */ + SharedMemObject<T> &operator=(SharedMemObject<T> &&rhs) + { + this->name_ = std::move(rhs.name_); +@@ -85,31 +108,61 @@ public: + return *this; + } + ++ /** ++ * \brief Operator-> for SharedMemObject. ++ * ++ * \return the object. ++ */ + T *operator->() + { + return obj_; + } + ++ /** ++ * \brief Operator-> for SharedMemObject. ++ * ++ * \return the object. ++ */ + const T *operator->() const + { + return obj_; + } + ++ /** ++ * \brief Operator* for SharedMemObject. ++ * ++ * \return the object. ++ */ + T &operator*() + { + return *obj_; + } + ++ /** ++ * \brief Operator* for SharedMemObject. ++ * ++ * \return the object. ++ */ + const T &operator*() const + { + return *obj_; + } + ++ /** ++ * \brief Gets the file descriptor for the underlaying storage file. ++ * ++ * \return the file descriptor. ++ */ + const SharedFD &fd() const + { + return fd_; + } + ++ /** ++ * \brief Operator bool() for SharedMemObject. ++ * ++ * \return true if the object is not null, false otherwise. ++ */ + explicit operator bool() const + { + return !!obj_; +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch b/users/flokli/ipu6-softisp/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch new file mode 100644 index 000000000000..ebda98d2672c --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch @@ -0,0 +1,354 @@ +From 3fa62a8e2f34c9794ba67e2565db8fef22938fa4 Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Sun, 22 Oct 2023 17:49:32 +0300 +Subject: [PATCH 06/25] libcamera: introduce SoftwareIsp class + +Doxygen documentation by Dennis Bonke. + +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + include/libcamera/internal/meson.build | 1 + + include/libcamera/internal/software_isp.h | 231 ++++++++++++++++++++++ + src/libcamera/meson.build | 1 + + src/libcamera/software_isp.cpp | 62 ++++++ + 4 files changed, 295 insertions(+) + create mode 100644 include/libcamera/internal/software_isp.h + create mode 100644 src/libcamera/software_isp.cpp + +diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build +index 5807dfd9..1325941d 100644 +--- a/include/libcamera/internal/meson.build ++++ b/include/libcamera/internal/meson.build +@@ -40,6 +40,7 @@ libcamera_internal_headers = files([ + 'pub_key.h', + 'request.h', + 'shared_mem_object.h', ++ 'software_isp.h', + 'source_paths.h', + 'sysfs.h', + 'v4l2_device.h', +diff --git a/include/libcamera/internal/software_isp.h b/include/libcamera/internal/software_isp.h +new file mode 100644 +index 00000000..42ff48ec +--- /dev/null ++++ b/include/libcamera/internal/software_isp.h +@@ -0,0 +1,231 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * software_isp.h - Interface for a software implementation of an ISP ++ */ ++ ++#pragma once ++ ++#include <functional> ++#include <initializer_list> ++#include <map> ++#include <memory> ++#include <string> ++#include <tuple> ++#include <vector> ++ ++#include <libcamera/base/class.h> ++#include <libcamera/base/log.h> ++#include <libcamera/base/signal.h> ++ ++#include <libcamera/geometry.h> ++ ++#include "libcamera/internal/pipeline_handler.h" ++ ++namespace libcamera { ++ ++class FrameBuffer; ++class PixelFormat; ++struct StreamConfiguration; ++ ++LOG_DECLARE_CATEGORY(SoftwareIsp) ++ ++/** ++ * \brief Base class for the Software ISP. ++ * ++ * Base class of the SoftwareIsp interface. ++ */ ++class SoftwareIsp ++{ ++public: ++ /** ++ * \brief Constructor for the SoftwareIsp object. ++ * \param[in] pipe The pipeline handler in use. ++ * \param[in] sensorControls The sensor controls. ++ */ ++ SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls); ++ virtual ~SoftwareIsp(); ++ ++ /** ++ * \brief Load a configuration from a file. ++ * \param[in] filename The file to load from. ++ * ++ * \return 0 on success. ++ */ ++ virtual int loadConfiguration(const std::string &filename) = 0; ++ ++ /** ++ * \brief Gets if there is a valid debayer object. ++ * ++ * \returns true if there is, false otherwise. ++ */ ++ virtual bool isValid() const = 0; ++ ++ /** ++ * \brief Get the supported output formats. ++ * \param[in] input The input format. ++ * ++ * \return all supported output formats or an empty vector if there are none. ++ */ ++ virtual std::vector<PixelFormat> formats(PixelFormat input) = 0; ++ ++ /** ++ * \brief Get the supported output sizes for the given input format and size. ++ * \param[in] inputFormat The input format. ++ * \param[in] inputSize The input size. ++ * ++ * \return The valid size ranges or an empty range if there are none. ++ */ ++ virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0; ++ ++ /** ++ * \brief Get the stride and the frame size. ++ * \param[in] pixelFormat The output format. ++ * \param[in] size The output size. ++ * ++ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config. ++ */ ++ virtual std::tuple<unsigned int, unsigned int> ++ strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size) = 0; ++ ++ /** ++ * \brief Configure the SwIspSimple object according to the passed in parameters. ++ * \param[in] inputCfg The input configuration. ++ * \param[in] outputCfgs The output configurations. ++ * \param[in] sensorControls The sensor controls. ++ * ++ * \return 0 on success, a negative errno on failure. ++ */ ++ virtual int configure(const StreamConfiguration &inputCfg, ++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, ++ const ControlInfoMap &sensorControls) = 0; ++ ++ /** ++ * \brief Exports the buffers for use in processing. ++ * \param[in] output The number of outputs requested. ++ * \param[in] count The number of planes. ++ * \param[out] buffers The exported buffers. ++ * ++ * \return count when successful, a negative return value if an error occurred. ++ */ ++ virtual int exportBuffers(unsigned int output, unsigned int count, ++ std::vector<std::unique_ptr<FrameBuffer>> *buffers) = 0; ++ ++ /** ++ * \brief Starts the Software ISP worker. ++ * ++ * \return 0 on success, any other value indicates an error. ++ */ ++ virtual int start() = 0; ++ ++ /** ++ * \brief Stops the Software ISP worker. ++ */ ++ virtual void stop() = 0; ++ ++ /** ++ * \brief Queues buffers for processing. ++ * \param[in] input The input framebuffer. ++ * \param[in] outputs The output framebuffers. ++ * ++ * \return 0 on success, a negative errno on failure ++ */ ++ virtual int queueBuffers(FrameBuffer *input, ++ const std::map<unsigned int, FrameBuffer *> &outputs) = 0; ++ ++ /** ++ * \brief Process the statistics gathered. ++ * \param[in] sensorControls The sensor controls. ++ */ ++ virtual void processStats(const ControlList &sensorControls) = 0; // rather merge with queueBuffers()? ++ ++ /** ++ * \brief Get the signal for when the sensor controls are set. ++ * ++ * \return The control list of the sensor controls. ++ */ ++ virtual Signal<const ControlList &> &getSignalSetSensorControls() = 0; ++ ++ /** ++ * \brief Signals that the input buffer is ready. ++ */ ++ Signal<FrameBuffer *> inputBufferReady; ++ /** ++ * \brief Signals that the output buffer is ready. ++ */ ++ Signal<FrameBuffer *> outputBufferReady; ++ ++ /** ++ * \brief Signals that the ISP stats are ready. ++ * ++ * The int parameter isn't actually used. ++ */ ++ Signal<int> ispStatsReady; ++}; ++ ++/** ++ * \brief Base class for the Software ISP Factory. ++ * ++ * Base class of the SoftwareIsp Factory. ++ */ ++class SoftwareIspFactoryBase ++{ ++public: ++ SoftwareIspFactoryBase(); ++ virtual ~SoftwareIspFactoryBase() = default; ++ ++ /** ++ * \brief Creates a SoftwareIsp object. ++ * \param[in] pipe The pipeline handler in use. ++ * \param[in] sensorControls The sensor controls. ++ * ++ * \return An unique pointer to the created SoftwareIsp object. ++ */ ++ static std::unique_ptr<SoftwareIsp> create(PipelineHandler *pipe, ++ const ControlInfoMap &sensorControls); ++ /** ++ * \brief Gives back a pointer to the factory. ++ * ++ * \return A static pointer to the factory instance. ++ */ ++ static SoftwareIspFactoryBase *&factory(); ++ ++private: ++ LIBCAMERA_DISABLE_COPY_AND_MOVE(SoftwareIspFactoryBase) ++ ++ static void registerType(SoftwareIspFactoryBase *factory); ++ virtual std::unique_ptr<SoftwareIsp> createInstance(PipelineHandler *pipe, ++ const ControlInfoMap &sensorControls) const = 0; ++}; ++ ++/** ++ * \brief Implementation for the Software ISP Factory. ++ */ ++template<typename _SoftwareIsp> ++class SoftwareIspFactory : public SoftwareIspFactoryBase ++{ ++public: ++ SoftwareIspFactory() ++ : SoftwareIspFactoryBase() ++ { ++ } ++ ++ /** ++ * \brief Creates an instance of a SoftwareIsp object. ++ * \param[in] pipe The pipeline handler in use. ++ * \param[in] sensorControls The sensor controls. ++ * ++ * \return An unique pointer to the created SoftwareIsp object. ++ */ ++ std::unique_ptr<SoftwareIsp> createInstance(PipelineHandler *pipe, ++ const ControlInfoMap &sensorControls) const override ++ { ++ return std::make_unique<_SoftwareIsp>(pipe, sensorControls); ++ } ++}; ++ ++#define REGISTER_SOFTWAREISP(softwareIsp) \ ++ static SoftwareIspFactory<softwareIsp> global_##softwareIsp##Factory; ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build +index 3c5e43df..86494663 100644 +--- a/src/libcamera/meson.build ++++ b/src/libcamera/meson.build +@@ -41,6 +41,7 @@ libcamera_sources = files([ + 'process.cpp', + 'pub_key.cpp', + 'request.cpp', ++ 'software_isp.cpp', + 'source_paths.cpp', + 'stream.cpp', + 'sysfs.cpp', +diff --git a/src/libcamera/software_isp.cpp b/src/libcamera/software_isp.cpp +new file mode 100644 +index 00000000..2ff97d70 +--- /dev/null ++++ b/src/libcamera/software_isp.cpp +@@ -0,0 +1,62 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * software_isp.cpp - Interface for a software implementation of an ISP ++ */ ++ ++#include "libcamera/internal/software_isp.h" ++ ++#include <libcamera/base/log.h> ++ ++namespace libcamera { ++ ++LOG_DEFINE_CATEGORY(SoftwareIsp) ++ ++SoftwareIsp::SoftwareIsp([[maybe_unused]] PipelineHandler *pipe, ++ [[maybe_unused]] const ControlInfoMap &sensorControls) ++{ ++} ++ ++SoftwareIsp::~SoftwareIsp() ++{ ++} ++ ++/* SoftwareIspFactoryBase */ ++ ++SoftwareIspFactoryBase::SoftwareIspFactoryBase() ++{ ++ registerType(this); ++} ++ ++void SoftwareIspFactoryBase::registerType(SoftwareIspFactoryBase *factory) ++{ ++ SoftwareIspFactoryBase *®istered = ++ SoftwareIspFactoryBase::factory(); ++ ++ ASSERT(!registered && factory); ++ registered = factory; ++} ++ ++SoftwareIspFactoryBase *&SoftwareIspFactoryBase::factory() ++{ ++ static SoftwareIspFactoryBase *factory; ++ return factory; ++} ++ ++std::unique_ptr<SoftwareIsp> ++SoftwareIspFactoryBase::create(PipelineHandler *pipe, ++ const ControlInfoMap &sensorControls) ++{ ++ SoftwareIspFactoryBase *factory = SoftwareIspFactoryBase::factory(); ++ if (!factory) ++ return nullptr; ++ ++ std::unique_ptr<SoftwareIsp> swIsp = factory->createInstance(pipe, sensorControls); ++ if (swIsp->isValid()) ++ return swIsp; ++ ++ return nullptr; ++} ++ ++} /* namespace libcamera */ +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch b/users/flokli/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch new file mode 100644 index 000000000000..9d6f2ac07fd8 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch @@ -0,0 +1,382 @@ +From ca3bb6ddf5307537aa05e43d3ec1ff7ffdc0efed Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Thu, 7 Dec 2023 13:30:27 +0100 +Subject: [PATCH 07/25] libcamera: software_isp: Add SwStats base class + +Add a virtual base class for CPU based software statistics gathering +implementations. + +The idea is for the implementations to offer a configure function + +functions to gather statistics on a line by line basis. This allows +CPU based software debayering to call into interlace debayering and +statistics gathering on a line by line bases while the input data +is still hot in the cache. + +This base class also allows the user of an implementation to specify +a window over which to gather statistics instead of processing the +whole frame; and it allows the implementation to choose to only +process 1/2, 1/4th, etc. of the lines instead of processing all +lines (in the window) by setting y_skip_mask_ from configure(). +Skipping columns is left up the line-processing functions provided +by the implementation. + +Doxygen documentation by Dennis Bonke. + +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + include/libcamera/internal/meson.build | 1 + + .../internal/software_isp/meson.build | 6 + + .../internal/software_isp/swisp_stats.h | 34 +++ + .../libcamera/internal/software_isp/swstats.h | 215 ++++++++++++++++++ + src/libcamera/meson.build | 1 + + src/libcamera/software_isp/meson.build | 5 + + src/libcamera/software_isp/swstats.cpp | 22 ++ + 7 files changed, 284 insertions(+) + create mode 100644 include/libcamera/internal/software_isp/meson.build + create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h + create mode 100644 include/libcamera/internal/software_isp/swstats.h + create mode 100644 src/libcamera/software_isp/meson.build + create mode 100644 src/libcamera/software_isp/swstats.cpp + +diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build +index 1325941d..caa533c4 100644 +--- a/include/libcamera/internal/meson.build ++++ b/include/libcamera/internal/meson.build +@@ -51,3 +51,4 @@ libcamera_internal_headers = files([ + ]) + + subdir('converter') ++subdir('software_isp') +diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build +new file mode 100644 +index 00000000..1c43acc4 +--- /dev/null ++++ b/include/libcamera/internal/software_isp/meson.build +@@ -0,0 +1,6 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++libcamera_internal_headers += files([ ++ 'swisp_stats.h', ++ 'swstats.h', ++]) +diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h +new file mode 100644 +index 00000000..07ba7d6a +--- /dev/null ++++ b/include/libcamera/internal/software_isp/swisp_stats.h +@@ -0,0 +1,34 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * swisp_stats.h - Statistics data format used by the software ISP and software IPA ++ */ ++ ++#pragma once ++ ++namespace libcamera { ++ ++/** ++ * \brief Struct that holds the statistics for the Software ISP. ++ */ ++struct SwIspStats { ++ /** ++ * \brief Holds the sum of all sampled red pixels. ++ */ ++ unsigned long sumR_; ++ /** ++ * \brief Holds the sum of all sampled green pixels. ++ */ ++ unsigned long sumG_; ++ /** ++ * \brief Holds the sum of all sampled blue pixels. ++ */ ++ unsigned long sumB_; ++ /** ++ * \brief A histogram of luminance values. ++ */ ++ unsigned int y_histogram[16]; ++}; ++ ++} /* namespace libcamera */ +diff --git a/include/libcamera/internal/software_isp/swstats.h b/include/libcamera/internal/software_isp/swstats.h +new file mode 100644 +index 00000000..dcac7064 +--- /dev/null ++++ b/include/libcamera/internal/software_isp/swstats.h +@@ -0,0 +1,215 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * swstats.h - software statistics base class ++ */ ++ ++#pragma once ++ ++#include <stdint.h> ++ ++#include <libcamera/base/log.h> ++#include <libcamera/base/signal.h> ++ ++#include <libcamera/geometry.h> ++ ++namespace libcamera { ++ ++class PixelFormat; ++struct SharedFD; ++struct StreamConfiguration; ++ ++LOG_DECLARE_CATEGORY(SwStats) ++ ++/** ++ * \class SwStats ++ * \brief Base class for the software ISP statistics. ++ * ++ * Base class for the software ISP statistics. ++ */ ++class SwStats ++{ ++public: ++ virtual ~SwStats() = 0; ++ ++ /** ++ * \brief Gets wether the statistics object is valid. ++ * ++ * \return true if it's valid, false otherwise. ++ */ ++ virtual bool isValid() const = 0; ++ ++ /** ++ * \brief Configure the statistics object for the passed in input format. ++ * \param[in] inputCfg The input format ++ * ++ * \return 0 on success, a negative errno value on failure. ++ */ ++ virtual int configure(const StreamConfiguration &inputCfg) = 0; ++ ++ /** ++ * \brief Get the file descriptor for the statistics. ++ * ++ * \return the file descriptor ++ */ ++ virtual const SharedFD &getStatsFD() = 0; ++ ++protected: ++ /** ++ * \brief Called when there is data to get statistics from. ++ * \param[in] src The input data ++ */ ++ typedef void (SwStats::*statsProcessFn)(const uint8_t *src[]); ++ /** ++ * \brief Called when the statistics gathering is done or when a new frame starts. ++ */ ++ typedef void (SwStats::*statsVoidFn)(); ++ ++ /* Variables set by configure(), used every line */ ++ /** ++ * \brief The function called when a line is ready for statistics processing. ++ * ++ * Used for line 0 and 1, repeating if there isn't a 3rd and a 4th line in the bayer order. ++ */ ++ statsProcessFn stats0_; ++ /** ++ * \brief The function called when a line is ready for statistics processing. ++ * ++ * Used for line 3 and 4, only needed if the bayer order has 4 different lines. ++ */ ++ statsProcessFn stats2_; ++ ++ /** ++ * \brief The memory used per pixel in bits. ++ */ ++ unsigned int bpp_; ++ /** ++ * \brief Skip lines where this bitmask is set in y. ++ */ ++ unsigned int y_skip_mask_; ++ ++ /** ++ * \brief Statistics window, set by setWindow(), used ever line. ++ */ ++ Rectangle window_; ++ ++ /** ++ * \brief The function called at the start of a frame. ++ */ ++ statsVoidFn startFrame_; ++ /** ++ * \brief The function called at the end of a frame. ++ */ ++ statsVoidFn finishFrame_; ++ /** ++ * \brief The size of the bayer pattern. ++ */ ++ Size patternSize_; ++ /** ++ * \brief The offset of x, applied to window_.x for bayer variants. ++ * ++ * This can either be 0 or 1. ++ */ ++ unsigned int x_shift_; ++ ++public: ++ /** ++ * \brief Get the pattern size. ++ * ++ * For some input-formats, e.g. Bayer data, processing is done multiple lines ++ * and/or columns at a time. Get width and height at which the (bayer) pattern ++ * repeats. Window values are rounded down to a multiple of this and the height ++ * also indicates if processLine2() should be called or not. ++ * This may only be called after a successful configure() call. ++ * ++ * \return the pattern size. ++ */ ++ const Size &patternSize() { return patternSize_; } ++ ++ /** ++ * \brief Specify window coordinates over which to gather statistics. ++ * \param[in] window The window object. ++ */ ++ void setWindow(Rectangle window) ++ { ++ window_ = window; ++ ++ window_.x &= ~(patternSize_.width - 1); ++ window_.x += x_shift_; ++ window_.y &= ~(patternSize_.height - 1); ++ ++ /* width_ - x_shift_ to make sure the window fits */ ++ window_.width -= x_shift_; ++ window_.width &= ~(patternSize_.width - 1); ++ window_.height &= ~(patternSize_.height - 1); ++ } ++ ++ /** ++ * \brief Reset state to start statistics gathering for a new frame. ++ * ++ * This may only be called after a successful setWindow() call. ++ */ ++ void startFrame() ++ { ++ (this->*startFrame_)(); ++ } ++ ++ /** ++ * \brief Process line 0. ++ * \param[in] y The y coordinate. ++ * \param[in] src The input data. ++ * ++ * This function processes line 0 for input formats with patternSize height == 1. ++ * It'll process line 0 and 1 for input formats with patternSize height >= 2. ++ * This function may only be called after a successful setWindow() call. ++ */ ++ void processLine0(unsigned int y, const uint8_t *src[]) ++ { ++ if ((y & y_skip_mask_) || y < (unsigned int)window_.y || ++ y >= (window_.y + window_.height)) ++ return; ++ ++ (this->*stats0_)(src); ++ } ++ ++ /** ++ * \brief Process line 2 and 3. ++ * \param[in] y The y coordinate. ++ * \param[in] src The input data. ++ * ++ * This function processes line 2 and 3 for input formats with patternSize height == 4. ++ * This function may only be called after a successful setWindow() call. ++ */ ++ void processLine2(unsigned int y, const uint8_t *src[]) ++ { ++ if ((y & y_skip_mask_) || y < (unsigned int)window_.y || ++ y >= (window_.y + window_.height)) ++ return; ++ ++ (this->*stats2_)(src); ++ } ++ ++ /** ++ * \brief Finish statistics calculation for the current frame. ++ * ++ * This may only be called after a successful setWindow() call. ++ */ ++ void finishFrame() ++ { ++ (this->*finishFrame_)(); ++ } ++ ++ /** ++ * \brief Signals that the statistics are ready. ++ * ++ * The int parameter isn't actually used. ++ */ ++ Signal<int> statsReady; ++}; ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build +index 86494663..3d63e8a2 100644 +--- a/src/libcamera/meson.build ++++ b/src/libcamera/meson.build +@@ -71,6 +71,7 @@ subdir('converter') + subdir('ipa') + subdir('pipeline') + subdir('proxy') ++subdir('software_isp') + + null_dep = dependency('', required : false) + +diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build +new file mode 100644 +index 00000000..9359075d +--- /dev/null ++++ b/src/libcamera/software_isp/meson.build +@@ -0,0 +1,5 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++libcamera_sources += files([ ++ 'swstats.cpp', ++]) +diff --git a/src/libcamera/software_isp/swstats.cpp b/src/libcamera/software_isp/swstats.cpp +new file mode 100644 +index 00000000..e65a7ada +--- /dev/null ++++ b/src/libcamera/software_isp/swstats.cpp +@@ -0,0 +1,22 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * swstats.cpp - software statistics base class ++ */ ++ ++#include "libcamera/internal/software_isp/swstats.h" ++ ++namespace libcamera { ++ ++LOG_DEFINE_CATEGORY(SwStats) ++ ++SwStats::~SwStats() ++{ ++} ++ ++} /* namespace libcamera */ +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch b/users/flokli/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch new file mode 100644 index 000000000000..d48eadd9949f --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch @@ -0,0 +1,272 @@ +From c1c43445cd4408010e500fe9d6b6424c77bcf75d Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Fri, 8 Dec 2023 12:50:57 +0100 +Subject: [PATCH 08/25] libcamera: software_isp: Add SwStatsCpu class + +Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use. + +Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Co-authored-by: Pavel Machek <pavel@ucw.cz> +Signed-off-by: Pavel Machek <pavel@ucw.cz> +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Marttico <g.martti@gmail.com> +Signed-off-by: Marttico <g.martti@gmail.com> +Co-authored-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + .../internal/software_isp/meson.build | 1 + + .../internal/software_isp/swstats_cpu.h | 44 +++++ + src/libcamera/software_isp/meson.build | 1 + + src/libcamera/software_isp/swstats_cpu.cpp | 164 ++++++++++++++++++ + 4 files changed, 210 insertions(+) + create mode 100644 include/libcamera/internal/software_isp/swstats_cpu.h + create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp + +diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build +index 1c43acc4..1d9e4018 100644 +--- a/include/libcamera/internal/software_isp/meson.build ++++ b/include/libcamera/internal/software_isp/meson.build +@@ -3,4 +3,5 @@ + libcamera_internal_headers += files([ + 'swisp_stats.h', + 'swstats.h', ++ 'swstats_cpu.h', + ]) +diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h +new file mode 100644 +index 00000000..8bb86e98 +--- /dev/null ++++ b/include/libcamera/internal/software_isp/swstats_cpu.h +@@ -0,0 +1,44 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * swstats_cpu.h - CPU based software statistics implementation ++ */ ++ ++#pragma once ++ ++#include "libcamera/internal/shared_mem_object.h" ++#include "libcamera/internal/software_isp/swisp_stats.h" ++#include "libcamera/internal/software_isp/swstats.h" ++ ++namespace libcamera { ++ ++/** ++ * \class SwStatsCpu ++ * \brief Implementation for the Software statistics on the CPU. ++ */ ++class SwStatsCpu : public SwStats ++{ ++public: ++ SwStatsCpu(); ++ ~SwStatsCpu() { } ++ ++ bool isValid() const { return sharedStats_.fd().isValid(); } ++ const SharedFD &getStatsFD() { return sharedStats_.fd(); } ++ int configure(const StreamConfiguration &inputCfg); ++private: ++ void statsBGGR10PLine0(const uint8_t *src[]); ++ void statsGBRG10PLine0(const uint8_t *src[]); ++ void resetStats(void); ++ void finishStats(void); ++ ++ SharedMemObject<SwIspStats> sharedStats_; ++ SwIspStats stats_; ++ bool swap_lines_; ++}; ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build +index 9359075d..d31c6217 100644 +--- a/src/libcamera/software_isp/meson.build ++++ b/src/libcamera/software_isp/meson.build +@@ -2,4 +2,5 @@ + + libcamera_sources += files([ + 'swstats.cpp', ++ 'swstats_cpu.cpp', + ]) +diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp +new file mode 100644 +index 00000000..59453d07 +--- /dev/null ++++ b/src/libcamera/software_isp/swstats_cpu.cpp +@@ -0,0 +1,164 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * swstats_cpu.cpp - CPU based software statistics implementation ++ */ ++ ++#include "libcamera/internal/software_isp/swstats_cpu.h" ++ ++#include <libcamera/base/log.h> ++ ++#include <libcamera/stream.h> ++ ++#include "libcamera/internal/bayer_format.h" ++ ++namespace libcamera { ++ ++SwStatsCpu::SwStatsCpu() ++ : SwStats() ++{ ++ sharedStats_ = SharedMemObject<SwIspStats>("softIsp_stats"); ++ if (!sharedStats_.fd().isValid()) ++ LOG(SwStats, Error) ++ << "Failed to create shared memory for statistics"; ++} ++ ++/* for brightness values in the 0 to 255 range: */ ++static const unsigned int BRIGHT_LVL = 200U << 8; ++static const unsigned int TOO_BRIGHT_LVL = 240U << 8; ++ ++static const unsigned int RED_Y_MUL = 77; /* 0.30 * 256 */ ++static const unsigned int GREEN_Y_MUL = 150; /* 0.59 * 256 */ ++static const unsigned int BLUE_Y_MUL = 29; /* 0.11 * 256 */ ++ ++#define SWISP_LINARO_START_LINE_STATS(pixel_t) \ ++ pixel_t r, g, g2, b; \ ++ unsigned int y_val; \ ++ \ ++ unsigned int sumR = 0; \ ++ unsigned int sumG = 0; \ ++ unsigned int sumB = 0; ++ ++#define SWISP_LINARO_ACCUMULATE_LINE_STATS(div) \ ++ sumR += r; \ ++ sumG += g; \ ++ sumB += b; \ ++ \ ++ y_val = r * RED_Y_MUL; \ ++ y_val += g * GREEN_Y_MUL; \ ++ y_val += b * BLUE_Y_MUL; \ ++ stats_.y_histogram[y_val / (256 * 16 * (div))]++; ++ ++#define SWISP_LINARO_FINISH_LINE_STATS() \ ++ stats_.sumR_ += sumR; \ ++ stats_.sumG_ += sumG; \ ++ stats_.sumB_ += sumB; ++ ++static inline __attribute__((always_inline)) void ++statsBayer10P(const int width, const uint8_t *src0, const uint8_t *src1, bool bggr, SwIspStats &stats_) ++{ ++ const int width_in_bytes = width * 5 / 4; ++ ++ SWISP_LINARO_START_LINE_STATS(uint8_t) ++ ++ for (int x = 0; x < width_in_bytes; x += 5) { ++ if (bggr) { ++ /* BGGR */ ++ b = src0[x]; ++ g = src0[x + 1]; ++ g2 = src1[x]; ++ r = src1[x + 1]; ++ } else { ++ /* GBRG */ ++ g = src0[x]; ++ b = src0[x + 1]; ++ r = src1[x]; ++ g2 = src1[x + 1]; ++ } ++ g = (g + g2) / 2; ++ ++ SWISP_LINARO_ACCUMULATE_LINE_STATS(1) ++ } ++ ++ SWISP_LINARO_FINISH_LINE_STATS() ++} ++ ++void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[]) ++{ ++ const uint8_t *src0 = src[1] + window_.x * 5 / 4; ++ const uint8_t *src1 = src[2] + window_.x * 5 / 4; ++ ++ if (swap_lines_) ++ std::swap(src0, src1); ++ ++ statsBayer10P(window_.width, src0, src1, true, stats_); ++} ++ ++void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[]) ++{ ++ const uint8_t *src0 = src[1] + window_.x * 5 / 4; ++ const uint8_t *src1 = src[2] + window_.x * 5 / 4; ++ ++ if (swap_lines_) ++ std::swap(src0, src1); ++ ++ statsBayer10P(window_.width, src0, src1, false, stats_); ++} ++ ++void SwStatsCpu::resetStats(void) ++{ ++ stats_.sumR_ = 0; ++ stats_.sumB_ = 0; ++ stats_.sumG_ = 0; ++ std::fill_n(stats_.y_histogram, 16, 0); ++} ++ ++void SwStatsCpu::finishStats(void) ++{ ++ *sharedStats_ = stats_; ++ statsReady.emit(0); ++} ++ ++int SwStatsCpu::configure(const StreamConfiguration &inputCfg) ++{ ++ BayerFormat bayerFormat = ++ BayerFormat::fromPixelFormat(inputCfg.pixelFormat); ++ ++ startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats; ++ finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats; ++ ++ if (bayerFormat.bitDepth == 10 && ++ bayerFormat.packing == BayerFormat::Packing::CSI2) { ++ bpp_ = 10; ++ patternSize_.height = 2; ++ patternSize_.width = 4; /* 5 bytes per *4* pixels */ ++ y_skip_mask_ = 0x02; /* Skip every 3th and 4th line */ ++ x_shift_ = 0; ++ ++ switch (bayerFormat.order) { ++ case BayerFormat::BGGR: ++ case BayerFormat::GRBG: ++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR10PLine0; ++ swap_lines_ = bayerFormat.order == BayerFormat::GRBG; ++ return 0; ++ case BayerFormat::GBRG: ++ case BayerFormat::RGGB: ++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsGBRG10PLine0; ++ swap_lines_ = bayerFormat.order == BayerFormat::RGGB; ++ return 0; ++ default: ++ break; ++ } ++ } ++ ++ LOG(SwStats, Info) ++ << "Unsupported input format " << inputCfg.pixelFormat.toString(); ++ return -EINVAL; ++} ++ ++} /* namespace libcamera */ +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch b/users/flokli/ipu6-softisp/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch new file mode 100644 index 000000000000..f43b3368e616 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch @@ -0,0 +1,272 @@ +From 8fc77447c0d76b0b52b19d23674049181c6cf8d2 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Mon, 11 Dec 2023 14:46:53 +0100 +Subject: [PATCH 09/25] libcamera: software_isp: Add Debayer base class + +Add a base class for debayer implementations. This is intended to be +suitable for both GPU (or otherwise) accelerated debayer implementations +as well as CPU based debayering. + +Doxygen documentation by Dennis Bonke. + +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + .../libcamera/internal/software_isp/debayer.h | 132 ++++++++++++++++++ + .../internal/software_isp/debayer_params.h | 43 ++++++ + .../internal/software_isp/meson.build | 2 + + src/libcamera/software_isp/debayer.cpp | 22 +++ + src/libcamera/software_isp/meson.build | 1 + + 5 files changed, 200 insertions(+) + create mode 100644 include/libcamera/internal/software_isp/debayer.h + create mode 100644 include/libcamera/internal/software_isp/debayer_params.h + create mode 100644 src/libcamera/software_isp/debayer.cpp + +diff --git a/include/libcamera/internal/software_isp/debayer.h b/include/libcamera/internal/software_isp/debayer.h +new file mode 100644 +index 00000000..39e6f393 +--- /dev/null ++++ b/include/libcamera/internal/software_isp/debayer.h +@@ -0,0 +1,132 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * debayer.h - debayering base class ++ */ ++ ++#pragma once ++ ++#include <stdint.h> ++ ++#include <libcamera/base/log.h> ++#include <libcamera/base/signal.h> ++ ++#include <libcamera/geometry.h> ++#include <libcamera/stream.h> ++ ++#include "libcamera/internal/software_isp/debayer_params.h" ++ ++namespace libcamera { ++ ++class FrameBuffer; ++ ++LOG_DECLARE_CATEGORY(Debayer) ++ ++/** ++ * \class Debayer ++ * \brief Base debayering class ++ * ++ * Base class that provides functions for setting up the debayering process. ++ */ ++class Debayer ++{ ++public: ++ virtual ~Debayer() = 0; ++ ++ /** ++ * \brief Configure the debayer object according to the passed in parameters. ++ * \param[in] inputCfg The input configuration. ++ * \param[in] outputCfgs The output configurations. ++ * ++ * \return 0 on success, a negative errno on failure. ++ */ ++ virtual int configure(const StreamConfiguration &inputCfg, ++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) = 0; ++ ++ /** ++ * \brief Get the width and height at which the bayer pattern repeats. ++ * \param[in] inputFormat The input format. ++ * ++ * \return pattern size or an empty size for unsupported inputFormats. ++ */ ++ virtual Size patternSize(PixelFormat inputFormat) = 0; ++ ++ /** ++ * \brief Get the supported output formats. ++ * \param[in] inputFormat The input format. ++ * ++ * \return all supported output formats or an empty vector if there are none. ++ */ ++ virtual std::vector<PixelFormat> formats(PixelFormat inputFormat) = 0; ++ ++ /** ++ * \brief Get the stride and the frame size. ++ * \param[in] outputFormat The output format. ++ * \param[in] size The output size. ++ * ++ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config. ++ */ ++ virtual std::tuple<unsigned int, unsigned int> ++ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0; ++ ++ /** ++ * \brief Process the bayer data into the requested format. ++ * \param[in] input The input buffer. ++ * \param[in] output The output buffer. ++ * \param[in] params The parameters to be used in debayering. ++ * ++ * \note DebayerParams is passed by value deliberately so that a copy is passed ++ * when this is run in another thread by invokeMethod(). ++ */ ++ virtual void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) = 0; ++ ++ /** ++ * \brief Get the supported output sizes for the given input format and size. ++ * \param[in] inputFormat The input format. ++ * \param[in] inputSize The input size. ++ * ++ * \return The valid size ranges or an empty range if there are none. ++ */ ++ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) ++ { ++ Size pattern_size = patternSize(inputFormat); ++ ++ if (pattern_size.isNull()) ++ return {}; ++ ++ /* ++ * For debayer interpolation a border of pattern-height x pattern-width ++ * is kept around the entire image. Combined with a minimum-size of ++ * pattern-height x pattern-width this means the input-size needs to be ++ * at least (3 * pattern-height) x (3 * pattern-width). ++ */ ++ if (inputSize.width < (3 * pattern_size.width) || ++ inputSize.height < (3 * pattern_size.height)) { ++ LOG(Debayer, Warning) ++ << "Input format size too small: " << inputSize.toString(); ++ return {}; ++ } ++ ++ return SizeRange(Size(pattern_size.width, pattern_size.height), ++ Size((inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1), ++ (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1)), ++ pattern_size.width, pattern_size.height); ++ } ++ ++ /** ++ * \brief Signals when the input buffer is ready. ++ */ ++ Signal<FrameBuffer *> inputBufferReady; ++ ++ /** ++ * \brief Signals when the output buffer is ready. ++ */ ++ Signal<FrameBuffer *> outputBufferReady; ++}; ++ ++} /* namespace libcamera */ +diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h +new file mode 100644 +index 00000000..8f515304 +--- /dev/null ++++ b/include/libcamera/internal/software_isp/debayer_params.h +@@ -0,0 +1,43 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * swstats.h - software statistics base class ++ */ ++ ++#pragma once ++ ++namespace libcamera { ++ ++/** ++ * \brief Struct to hold the debayer parameters. ++ */ ++struct DebayerParams { ++ /** ++ * \brief Red Gain. ++ * ++ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc. ++ */ ++ unsigned int gainR; ++ /** ++ * \brief Green Gain. ++ * ++ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc. ++ */ ++ unsigned int gainG; ++ /** ++ * \brief Blue Gain. ++ * ++ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc. ++ */ ++ unsigned int gainB; ++ /** ++ * \brief Gamma correction, 1.0 is no correction. ++ */ ++ float gamma; ++}; ++ ++} /* namespace libcamera */ +diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build +index 1d9e4018..7e40925e 100644 +--- a/include/libcamera/internal/software_isp/meson.build ++++ b/include/libcamera/internal/software_isp/meson.build +@@ -1,6 +1,8 @@ + # SPDX-License-Identifier: CC0-1.0 + + libcamera_internal_headers += files([ ++ 'debayer.h', ++ 'debayer_params.h', + 'swisp_stats.h', + 'swstats.h', + 'swstats_cpu.h', +diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp +new file mode 100644 +index 00000000..442da1ac +--- /dev/null ++++ b/src/libcamera/software_isp/debayer.cpp +@@ -0,0 +1,22 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * debayer.cpp - debayer base class ++ */ ++ ++#include "libcamera/internal/software_isp/debayer.h" ++ ++namespace libcamera { ++ ++LOG_DEFINE_CATEGORY(Debayer) ++ ++Debayer::~Debayer() ++{ ++} ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build +index d31c6217..d4ae5ac7 100644 +--- a/src/libcamera/software_isp/meson.build ++++ b/src/libcamera/software_isp/meson.build +@@ -1,6 +1,7 @@ + # SPDX-License-Identifier: CC0-1.0 + + libcamera_sources += files([ ++ 'debayer.cpp', + 'swstats.cpp', + 'swstats_cpu.cpp', + ]) +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch b/users/flokli/ipu6-softisp/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch new file mode 100644 index 000000000000..56bc87336c2e --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch @@ -0,0 +1,727 @@ +From 7eb7164ed7d90ea4cf9ec7e4f35fa8efa25f35e9 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Mon, 11 Dec 2023 17:00:17 +0100 +Subject: [PATCH 10/25] libcamera: software_isp: Add DebayerCpu class + +Add CPU based debayering implementation. This initial implementation +only supports debayering packed 10 bits per pixel bayer data in +the 4 standard bayer orders. + +Doxygen documentation by Dennis Bonke. + +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Co-authored-by: Pavel Machek <pavel@ucw.cz> +Signed-off-by: Pavel Machek <pavel@ucw.cz> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + .../internal/software_isp/debayer_cpu.h | 131 +++++ + .../internal/software_isp/meson.build | 1 + + src/libcamera/software_isp/debayer_cpu.cpp | 528 ++++++++++++++++++ + src/libcamera/software_isp/meson.build | 1 + + 4 files changed, 661 insertions(+) + create mode 100644 include/libcamera/internal/software_isp/debayer_cpu.h + create mode 100644 src/libcamera/software_isp/debayer_cpu.cpp + +diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h +new file mode 100644 +index 00000000..78573f44 +--- /dev/null ++++ b/include/libcamera/internal/software_isp/debayer_cpu.h +@@ -0,0 +1,131 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * debayer_cpu.h - CPU based debayering header ++ */ ++ ++#pragma once ++ ++#include <memory> ++#include <stdint.h> ++#include <vector> ++ ++#include <libcamera/base/object.h> ++ ++#include "libcamera/internal/software_isp/swstats_cpu.h" ++#include "libcamera/internal/software_isp/debayer.h" ++ ++namespace libcamera { ++ ++/** ++ * \class DebayerCpu ++ * \brief Class for debayering on the CPU ++ * ++ * Implementation for CPU based debayering ++ */ ++class DebayerCpu : public Debayer, public Object ++{ ++public: ++ /* ++ * FIXME this should be a plain (implementation independent) SwStats ++ * this can be fixed once getStats() is dropped. ++ */ ++ /** ++ * \brief Constructs a DebayerCpu object. ++ * \param[in] stats Pointer to the stats object to use. ++ */ ++ DebayerCpu(std::unique_ptr<SwStatsCpu> stats); ++ ~DebayerCpu(); ++ ++ /* ++ * Setup the Debayer object according to the passed in parameters. ++ * Return 0 on success, a negative errno value on failure ++ * (unsupported parameters). ++ */ ++ int configure(const StreamConfiguration &inputCfg, ++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs); ++ ++ /* ++ * Get width and height at which the bayer-pattern repeats. ++ * Return pattern-size or an empty Size for an unsupported inputFormat. ++ */ ++ Size patternSize(PixelFormat inputFormat); ++ ++ std::vector<PixelFormat> formats(PixelFormat input); ++ std::tuple<unsigned int, unsigned int> ++ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); ++ ++ void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params); ++ ++ /** ++ * \brief Get the file descriptor for the statistics. ++ * ++ * \return the file descriptor pointing to the statistics. ++ */ ++ const SharedFD &getStatsFD() { return stats_->getStatsFD(); } ++ ++ /** ++ * \brief Get the output frame size. ++ * ++ * \return The output frame size. ++ */ ++ unsigned int frameSize() { return outputConfig_.frameSize; } ++private: ++ void initLinePointers(const uint8_t *linePointers[], const uint8_t *src); ++ void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src); ++ void process2(const uint8_t *src, uint8_t *dst); ++ void process4(const uint8_t *src, uint8_t *dst); ++ /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */ ++ void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); ++ void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); ++ void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); ++ void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); ++ ++ typedef void (DebayerCpu::*debayerFn)(uint8_t *dst, const uint8_t *src[]); ++ ++ struct DebayerInputConfig { ++ Size patternSize; ++ unsigned int bpp; /* Memory used per pixel, not precision */ ++ unsigned int stride; ++ std::vector<PixelFormat> outputFormats; ++ }; ++ ++ struct DebayerOutputConfig { ++ unsigned int bpp; /* Memory used per pixel, not precision */ ++ unsigned int stride; ++ unsigned int frameSize; ++ }; ++ ++ int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); ++ int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config); ++ int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat); ++ ++ uint8_t gamma_[1024]; ++ uint8_t red_[256]; ++ uint8_t green_[256]; ++ uint8_t blue_[256]; ++ debayerFn debayer0_; ++ debayerFn debayer1_; ++ debayerFn debayer2_; ++ debayerFn debayer3_; ++ Rectangle window_; ++ DebayerInputConfig inputConfig_; ++ DebayerOutputConfig outputConfig_; ++ std::unique_ptr<SwStatsCpu> stats_; ++ uint8_t *lineBuffers_[5]; ++ unsigned int lineBufferIndex_; ++ bool enableInputMemcpy_; ++ float gamma_correction_; ++ int measuredFrames_; ++ int64_t frameProcessTime_; ++ /* Skip 30 frames for things to stabilize then measure 30 frames */ ++ static const int framesToSkip = 30; ++ static const int framesToMeasure = 60; ++}; ++ ++} /* namespace libcamera */ +diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build +index 7e40925e..b5a0d737 100644 +--- a/include/libcamera/internal/software_isp/meson.build ++++ b/include/libcamera/internal/software_isp/meson.build +@@ -2,6 +2,7 @@ + + libcamera_internal_headers += files([ + 'debayer.h', ++ 'debayer_cpu.h', + 'debayer_params.h', + 'swisp_stats.h', + 'swstats.h', +diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp +new file mode 100644 +index 00000000..e0c3c658 +--- /dev/null ++++ b/src/libcamera/software_isp/debayer_cpu.cpp +@@ -0,0 +1,528 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * Copyright (C) 2023, Red Hat Inc. ++ * ++ * Authors: ++ * Hans de Goede <hdegoede@redhat.com> ++ * ++ * debayer_cpu.cpp - CPU based debayering class ++ */ ++ ++#include "libcamera/internal/software_isp/debayer_cpu.h" ++ ++#include <math.h> ++#include <stdlib.h> ++#include <time.h> ++ ++#include <libcamera/formats.h> ++ ++#include "libcamera/internal/bayer_format.h" ++#include "libcamera/internal/framebuffer.h" ++#include "libcamera/internal/mapped_framebuffer.h" ++ ++namespace libcamera { ++ ++DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats) ++ : stats_(std::move(stats)), gamma_correction_(1.0) ++{ ++#ifdef __x86_64__ ++ enableInputMemcpy_ = false; ++#else ++ enableInputMemcpy_ = true; ++#endif ++ /* Initialize gamma to 1.0 curve */ ++ for (int i = 0; i < 1024; i++) ++ gamma_[i] = i / 4; ++ ++ for (int i = 0; i < 5; i++) ++ lineBuffers_[i] = NULL; ++} ++ ++DebayerCpu::~DebayerCpu() ++{ ++ for (int i = 0; i < 5; i++) ++ free(lineBuffers_[i]); ++} ++ ++// RGR ++// GBG ++// RGR ++#define BGGR_BGR888(p, n, div) \ ++ *dst++ = blue_[curr[x] / (div)]; \ ++ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \ ++ *dst++ = red_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \ ++ x++; ++ ++// GBG ++// RGR ++// GBG ++#define GRBG_BGR888(p, n, div) \ ++ *dst++ = blue_[(prev[x] + next[x]) / (2 * (div))]; \ ++ *dst++ = green_[curr[x] / (div)]; \ ++ *dst++ = red_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \ ++ x++; ++ ++// GRG ++// BGB ++// GRG ++#define GBRG_BGR888(p, n, div) \ ++ *dst++ = blue_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \ ++ *dst++ = green_[curr[x] / (div)]; \ ++ *dst++ = red_[(prev[x] + next[x]) / (2 * (div))]; \ ++ x++; ++ ++// BGB ++// GRG ++// BGB ++#define RGGB_BGR888(p, n, div) \ ++ *dst++ = blue_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \ ++ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \ ++ *dst++ = red_[curr[x] / (div)]; \ ++ x++; ++ ++void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ const int width_in_bytes = window_.width * 5 / 4; ++ const uint8_t *prev = (const uint8_t *)src[0]; ++ const uint8_t *curr = (const uint8_t *)src[1]; ++ const uint8_t *next = (const uint8_t *)src[2]; ++ ++ /* ++ * For the first pixel getting a pixel from the previous column uses ++ * x - 2 to skip the 5th byte with least-significant bits for 4 pixels. ++ * Same for last pixel (uses x + 2) and looking at the next column. ++ * x++ in the for-loop skips the 5th byte with 4 x 2 lsb-s for 10bit packed. ++ */ ++ for (int x = 0; x < width_in_bytes; x++) { ++ /* Even pixel */ ++ BGGR_BGR888(2, 1, 1) ++ /* Odd pixel BGGR -> GBRG */ ++ GBRG_BGR888(1, 1, 1) ++ /* Same thing for next 2 pixels */ ++ BGGR_BGR888(1, 1, 1) ++ GBRG_BGR888(1, 2, 1) ++ } ++} ++ ++void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ const int width_in_bytes = window_.width * 5 / 4; ++ const uint8_t *prev = (const uint8_t *)src[0]; ++ const uint8_t *curr = (const uint8_t *)src[1]; ++ const uint8_t *next = (const uint8_t *)src[2]; ++ ++ for (int x = 0; x < width_in_bytes; x++) { ++ /* Even pixel */ ++ GRBG_BGR888(2, 1, 1) ++ /* Odd pixel GRBG -> RGGB */ ++ RGGB_BGR888(1, 1, 1) ++ /* Same thing for next 2 pixels */ ++ GRBG_BGR888(1, 1, 1) ++ RGGB_BGR888(1, 2, 1) ++ } ++} ++ ++void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ const int width_in_bytes = window_.width * 5 / 4; ++ const uint8_t *prev = (const uint8_t *)src[0]; ++ const uint8_t *curr = (const uint8_t *)src[1]; ++ const uint8_t *next = (const uint8_t *)src[2]; ++ ++ for (int x = 0; x < width_in_bytes; x++) { ++ /* Even pixel */ ++ GBRG_BGR888(2, 1, 1) ++ /* Odd pixel GBGR -> BGGR */ ++ BGGR_BGR888(1, 1, 1) ++ /* Same thing for next 2 pixels */ ++ GBRG_BGR888(1, 1, 1) ++ BGGR_BGR888(1, 2, 1) ++ } ++} ++ ++void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ const int width_in_bytes = window_.width * 5 / 4; ++ const uint8_t *prev = (const uint8_t *)src[0]; ++ const uint8_t *curr = (const uint8_t *)src[1]; ++ const uint8_t *next = (const uint8_t *)src[2]; ++ ++ for (int x = 0; x < width_in_bytes; x++) { ++ /* Even pixel */ ++ RGGB_BGR888(2, 1, 1) ++ /* Odd pixel RGGB -> GRBG*/ ++ GRBG_BGR888(1, 1, 1) ++ /* Same thing for next 2 pixels */ ++ RGGB_BGR888(1, 1, 1) ++ GRBG_BGR888(1, 2, 1) ++ } ++} ++ ++static bool isStandardBayerOrder(BayerFormat::Order order) ++{ ++ return order == BayerFormat::BGGR || order == BayerFormat::GBRG || ++ order == BayerFormat::GRBG || order == BayerFormat::RGGB; ++} ++ ++int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config) ++{ ++ BayerFormat bayerFormat = ++ BayerFormat::fromPixelFormat(inputFormat); ++ ++ if (bayerFormat.bitDepth == 10 && ++ bayerFormat.packing == BayerFormat::Packing::CSI2 && ++ isStandardBayerOrder(bayerFormat.order)) { ++ config.bpp = 10; ++ config.patternSize.width = 4; /* 5 bytes per *4* pixels */ ++ config.patternSize.height = 2; ++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 }); ++ return 0; ++ } ++ ++ LOG(Debayer, Info) ++ << "Unsupported input format " << inputFormat.toString(); ++ return -EINVAL; ++} ++ ++int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config) ++{ ++ if (outputFormat == formats::RGB888) { ++ config.bpp = 24; ++ return 0; ++ } ++ ++ LOG(Debayer, Info) ++ << "Unsupported output format " << outputFormat.toString(); ++ return -EINVAL; ++} ++ ++/* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */ ++int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat) ++{ ++ BayerFormat bayerFormat = ++ BayerFormat::fromPixelFormat(inputFormat); ++ ++ if (bayerFormat.bitDepth == 10 && ++ bayerFormat.packing == BayerFormat::Packing::CSI2) { ++ switch (bayerFormat.order) { ++ case BayerFormat::BGGR: ++ debayer0_ = &DebayerCpu::debayer10P_BGBG_BGR888; ++ debayer1_ = &DebayerCpu::debayer10P_GRGR_BGR888; ++ return 0; ++ case BayerFormat::GBRG: ++ debayer0_ = &DebayerCpu::debayer10P_GBGB_BGR888; ++ debayer1_ = &DebayerCpu::debayer10P_RGRG_BGR888; ++ return 0; ++ case BayerFormat::GRBG: ++ debayer0_ = &DebayerCpu::debayer10P_GRGR_BGR888; ++ debayer1_ = &DebayerCpu::debayer10P_BGBG_BGR888; ++ return 0; ++ case BayerFormat::RGGB: ++ debayer0_ = &DebayerCpu::debayer10P_RGRG_BGR888; ++ debayer1_ = &DebayerCpu::debayer10P_GBGB_BGR888; ++ return 0; ++ default: ++ break; ++ } ++ } ++ ++ LOG(Debayer, Error) << "Unsupported input output format combination"; ++ return -EINVAL; ++} ++ ++int DebayerCpu::configure(const StreamConfiguration &inputCfg, ++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) ++{ ++ if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) ++ return -EINVAL; ++ ++ if (stats_->configure(inputCfg) != 0) ++ return -EINVAL; ++ ++ const Size &stats_pattern_size = stats_->patternSize(); ++ if (inputConfig_.patternSize.width != stats_pattern_size.width || ++ inputConfig_.patternSize.height != stats_pattern_size.height) { ++ LOG(Debayer, Error) ++ << "mismatching stats and debayer pattern sizes for " ++ << inputCfg.pixelFormat.toString(); ++ return -EINVAL; ++ } ++ ++ inputConfig_.stride = inputCfg.stride; ++ ++ if (outputCfgs.size() != 1) { ++ LOG(Debayer, Error) ++ << "Unsupported number of output streams: " ++ << outputCfgs.size(); ++ return -EINVAL; ++ } ++ ++ const StreamConfiguration &outputCfg = outputCfgs[0]; ++ SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size); ++ std::tie(outputConfig_.stride, outputConfig_.frameSize) = ++ strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size); ++ ++ if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) { ++ LOG(Debayer, Error) ++ << "Invalid output size/stride: " ++ << "\n " << outputCfg.size << " (" << outSizeRange << ")" ++ << "\n " << outputCfg.stride << " (" << outputConfig_.stride << ")"; ++ return -EINVAL; ++ } ++ ++ if (setDebayerFunctions(inputCfg.pixelFormat, outputCfg.pixelFormat) != 0) ++ return -EINVAL; ++ ++ window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & ++ ~(inputConfig_.patternSize.width - 1); ++ window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & ++ ~(inputConfig_.patternSize.height - 1); ++ window_.width = outputCfg.size.width; ++ window_.height = outputCfg.size.height; ++ ++ /* Don't pass x,y since process() already adjusts src before passing it */ ++ stats_->setWindow(Rectangle(window_.size())); ++ ++ for (unsigned int i = 0; ++ i < (inputConfig_.patternSize.height + 1) && enableInputMemcpy_; ++ i++) { ++ /* pad with patternSize.Width on both left and right side */ ++ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) * ++ inputConfig_.bpp / 8; ++ ++ free(lineBuffers_[i]); ++ lineBuffers_[i] = (uint8_t *)malloc(lineLength); ++ if (!lineBuffers_[i]) ++ return -ENOMEM; ++ } ++ ++ measuredFrames_ = 0; ++ frameProcessTime_ = 0; ++ ++ return 0; ++} ++ ++Size DebayerCpu::patternSize(PixelFormat inputFormat) ++{ ++ DebayerCpu::DebayerInputConfig config; ++ ++ if (getInputConfig(inputFormat, config) != 0) ++ return {}; ++ ++ return config.patternSize; ++} ++ ++std::vector<PixelFormat> DebayerCpu::formats(PixelFormat inputFormat) ++{ ++ DebayerCpu::DebayerInputConfig config; ++ ++ if (getInputConfig(inputFormat, config) != 0) ++ return std::vector<PixelFormat>(); ++ ++ return config.outputFormats; ++} ++ ++std::tuple<unsigned int, unsigned int> ++DebayerCpu::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) ++{ ++ DebayerCpu::DebayerOutputConfig config; ++ ++ if (getOutputConfig(outputFormat, config) != 0) ++ return std::make_tuple(0, 0); ++ ++ /* round up to multiple of 8 for 64 bits alignment */ ++ unsigned int stride = (size.width * config.bpp / 8 + 7) & ~7; ++ ++ return std::make_tuple(stride, stride * size.height); ++} ++ ++void DebayerCpu::initLinePointers(const uint8_t *linePointers[], const uint8_t *src) ++{ ++ const int patternHeight = inputConfig_.patternSize.height; ++ ++ for (int i = 0; i < patternHeight; i++) ++ linePointers[i + 1] = src + ++ (-patternHeight / 2 + i) * (int)inputConfig_.stride; ++ ++ if (!enableInputMemcpy_) ++ return; ++ ++ for (int i = 0; i < patternHeight; i++) { ++ /* pad with patternSize.Width on both left and right side */ ++ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) * ++ inputConfig_.bpp / 8; ++ int padding = inputConfig_.patternSize.width * inputConfig_.bpp / 8; ++ ++ memcpy(lineBuffers_[i], linePointers[i + 1] - padding, lineLength); ++ linePointers[i + 1] = lineBuffers_[i] + padding; ++ } ++ ++ /* Point lineBufferIndex_ to first unused lineBuffer */ ++ lineBufferIndex_ = patternHeight; ++} ++ ++void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src) ++{ ++ const int patternHeight = inputConfig_.patternSize.height; ++ ++ for (int i = 0; i < patternHeight; i++) ++ linePointers[i] = linePointers[i + 1]; ++ ++ linePointers[patternHeight] = src + ++ (patternHeight / 2) * (int)inputConfig_.stride; ++ ++ if (!enableInputMemcpy_) ++ return; ++ ++ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) * ++ inputConfig_.bpp / 8; ++ int padding = inputConfig_.patternSize.width * inputConfig_.bpp / 8; ++ memcpy(lineBuffers_[lineBufferIndex_], linePointers[patternHeight] - padding, lineLength); ++ linePointers[patternHeight] = lineBuffers_[lineBufferIndex_] + padding; ++ ++ lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1); ++} ++ ++void DebayerCpu::process2(const uint8_t *src, uint8_t *dst) ++{ ++ const unsigned int y_end = window_.y + window_.height; ++ const uint8_t *linePointers[3]; ++ ++ /* Adjust src to top left corner of the window */ ++ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8; ++ ++ initLinePointers(linePointers, src); ++ ++ for (unsigned int y = window_.y; y < y_end; y += 2) { ++ shiftLinePointers(linePointers, src); ++ stats_->processLine0(y, linePointers); ++ (this->*debayer0_)(dst, linePointers); ++ src += inputConfig_.stride; ++ dst += outputConfig_.stride; ++ ++ shiftLinePointers(linePointers, src); ++ (this->*debayer1_)(dst, linePointers); ++ src += inputConfig_.stride; ++ dst += outputConfig_.stride; ++ } ++} ++ ++void DebayerCpu::process4(const uint8_t *src, uint8_t *dst) ++{ ++ const unsigned int y_end = window_.y + window_.height; ++ const uint8_t *linePointers[5]; ++ ++ /* Adjust src to top left corner of the window */ ++ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8; ++ ++ initLinePointers(linePointers, src); ++ ++ for (unsigned int y = window_.y; y < y_end; y += 4) { ++ shiftLinePointers(linePointers, src); ++ stats_->processLine0(y, linePointers); ++ (this->*debayer0_)(dst, linePointers); ++ src += inputConfig_.stride; ++ dst += outputConfig_.stride; ++ ++ shiftLinePointers(linePointers, src); ++ (this->*debayer1_)(dst, linePointers); ++ src += inputConfig_.stride; ++ dst += outputConfig_.stride; ++ ++ shiftLinePointers(linePointers, src); ++ stats_->processLine2(y, linePointers); ++ (this->*debayer2_)(dst, linePointers); ++ src += inputConfig_.stride; ++ dst += outputConfig_.stride; ++ ++ shiftLinePointers(linePointers, src); ++ (this->*debayer3_)(dst, linePointers); ++ src += inputConfig_.stride; ++ dst += outputConfig_.stride; ++ } ++} ++ ++static inline int64_t timeDiff(timespec &after, timespec &before) ++{ ++ return (after.tv_sec - before.tv_sec) * 1000000000LL + ++ (int64_t)after.tv_nsec - (int64_t)before.tv_nsec; ++} ++ ++void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) ++{ ++ timespec frameStartTime; ++ ++ if (measuredFrames_ < DebayerCpu::framesToMeasure) { ++ frameStartTime = {}; ++ clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime); ++ } ++ ++ /* Apply DebayerParams */ ++ if (params.gamma != gamma_correction_) { ++ for (int i = 0; i < 1024; i++) ++ gamma_[i] = 255 * powf(i / 1023.0, params.gamma); ++ ++ gamma_correction_ = params.gamma; ++ } ++ ++ for (int i = 0; i < 256; i++) { ++ int idx; ++ ++ /* Apply gamma after gain! */ ++ idx = std::min({ i * params.gainR / 64U, 1023U }); ++ red_[i] = gamma_[idx]; ++ ++ idx = std::min({ i * params.gainG / 64U, 1023U }); ++ green_[i] = gamma_[idx]; ++ ++ idx = std::min({ i * params.gainB / 64U, 1023U }); ++ blue_[i] = gamma_[idx]; ++ } ++ ++ /* Copy metadata from the input buffer */ ++ FrameMetadata &metadata = output->_d()->metadata(); ++ metadata.status = input->metadata().status; ++ metadata.sequence = input->metadata().sequence; ++ metadata.timestamp = input->metadata().timestamp; ++ ++ MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read); ++ MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write); ++ if (!in.isValid() || !out.isValid()) { ++ LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; ++ metadata.status = FrameMetadata::FrameError; ++ return; ++ } ++ ++ stats_->startFrame(); ++ ++ if (inputConfig_.patternSize.height == 2) ++ process2(in.planes()[0].data(), out.planes()[0].data()); ++ else ++ process4(in.planes()[0].data(), out.planes()[0].data()); ++ ++ metadata.planes()[0].bytesused = out.planes()[0].size(); ++ ++ /* Measure before emitting signals */ ++ if (measuredFrames_ < DebayerCpu::framesToMeasure && ++ ++measuredFrames_ > DebayerCpu::framesToSkip) { ++ timespec frameEndTime = {}; ++ clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime); ++ frameProcessTime_ += timeDiff(frameEndTime, frameStartTime); ++ if (measuredFrames_ == DebayerCpu::framesToMeasure) { ++ const int measuredFrames = DebayerCpu::framesToMeasure - ++ DebayerCpu::framesToSkip; ++ LOG(Debayer, Info) ++ << "Processed " << measuredFrames ++ << " frames in " << frameProcessTime_ / 1000 << "us, " ++ << frameProcessTime_ / (1000 * measuredFrames) ++ << " us/frame"; ++ } ++ } ++ ++ stats_->finishFrame(); ++ outputBufferReady.emit(output); ++ inputBufferReady.emit(input); ++} ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build +index d4ae5ac7..6d7a44d7 100644 +--- a/src/libcamera/software_isp/meson.build ++++ b/src/libcamera/software_isp/meson.build +@@ -2,6 +2,7 @@ + + libcamera_sources += files([ + 'debayer.cpp', ++ 'debayer_cpu.cpp', + 'swstats.cpp', + 'swstats_cpu.cpp', + ]) +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch b/users/flokli/ipu6-softisp/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch new file mode 100644 index 000000000000..d5fd94f6af37 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch @@ -0,0 +1,256 @@ +From 05b353f1e45f2af0d0989261210b4bedef5144de Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Mon, 11 Dec 2023 23:41:58 +0300 +Subject: [PATCH 11/25] libcamera: ipa: add Soft IPA common files + +Define the Soft IPA main and event interfaces, add IPASoftBase +class the Soft IPA implementation inherit from. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + Documentation/Doxyfile.in | 1 + + include/libcamera/ipa/meson.build | 1 + + include/libcamera/ipa/soft.mojom | 29 ++++++++++++ + src/ipa/simple/common/meson.build | 17 +++++++ + src/ipa/simple/common/soft_base.cpp | 73 +++++++++++++++++++++++++++++ + src/ipa/simple/common/soft_base.h | 50 ++++++++++++++++++++ + src/ipa/simple/meson.build | 3 ++ + 7 files changed, 174 insertions(+) + create mode 100644 include/libcamera/ipa/soft.mojom + create mode 100644 src/ipa/simple/common/meson.build + create mode 100644 src/ipa/simple/common/soft_base.cpp + create mode 100644 src/ipa/simple/common/soft_base.h + create mode 100644 src/ipa/simple/meson.build + +diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in +index a86ea6c1..2be8d47b 100644 +--- a/Documentation/Doxyfile.in ++++ b/Documentation/Doxyfile.in +@@ -44,6 +44,7 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \ + @TOP_SRCDIR@/src/libcamera/pipeline/ \ + @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ + @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ ++ @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \ + @TOP_BUILDDIR@/src/libcamera/proxy/ + + EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ +diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build +index f3b4881c..894e38a6 100644 +--- a/include/libcamera/ipa/meson.build ++++ b/include/libcamera/ipa/meson.build +@@ -65,6 +65,7 @@ pipeline_ipa_mojom_mapping = { + 'ipu3': 'ipu3.mojom', + 'rkisp1': 'rkisp1.mojom', + 'rpi/vc4': 'raspberrypi.mojom', ++ 'simple/simple': 'soft.mojom', + 'vimc': 'vimc.mojom', + } + +diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom +new file mode 100644 +index 00000000..2dae652b +--- /dev/null ++++ b/include/libcamera/ipa/soft.mojom +@@ -0,0 +1,29 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++/* ++ * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry. ++ * \todo Add a way to tell SoftIPA the list of params SoftISP accepts? ++ */ ++ ++module ipa.soft; ++ ++import "include/libcamera/ipa/core.mojom"; ++ ++interface IPASoftInterface { ++ init(libcamera.IPASettings settings, ++ libcamera.SharedFD fdStats, ++ libcamera.SharedFD fdParams, ++ libcamera.ControlInfoMap sensorCtrlInfoMap) ++ => (int32 ret); ++ start() => (int32 ret); ++ stop(); ++ configure(libcamera.ControlInfoMap sensorCtrlInfoMap) ++ => (int32 ret); ++ ++ [async] processStats(libcamera.ControlList sensorControls); ++}; ++ ++interface IPASoftEventInterface { ++ setSensorControls(libcamera.ControlList sensorControls); ++ setIspParams(int32 dummy); ++}; +diff --git a/src/ipa/simple/common/meson.build b/src/ipa/simple/common/meson.build +new file mode 100644 +index 00000000..023e617b +--- /dev/null ++++ b/src/ipa/simple/common/meson.build +@@ -0,0 +1,17 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++soft_ipa_common_sources = files([ ++ 'soft_base.cpp', ++]) ++ ++soft_ipa_common_includes = [ ++ include_directories('..'), ++] ++ ++soft_ipa_common_deps = [ ++ libcamera_private, ++] ++ ++soft_ipa_common_lib = static_library('soft_ipa_common', soft_ipa_common_sources, ++ include_directories : soft_ipa_common_includes, ++ dependencies : soft_ipa_common_deps) +diff --git a/src/ipa/simple/common/soft_base.cpp b/src/ipa/simple/common/soft_base.cpp +new file mode 100644 +index 00000000..b4ed9023 +--- /dev/null ++++ b/src/ipa/simple/common/soft_base.cpp +@@ -0,0 +1,73 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * soft-base.cpp - Software IPA base class ++ */ ++ ++#include "soft_base.h" ++ ++#include <sys/mman.h> ++ ++#include <libcamera/base/file.h> ++#include <libcamera/base/log.h> ++ ++#include <libcamera/control_ids.h> ++ ++namespace libcamera { ++ ++LOG_DEFINE_CATEGORY(IPASoft) ++ ++namespace ipa::soft { ++ ++IPASoftBase::IPASoftBase() ++{ ++} ++ ++IPASoftBase::~IPASoftBase() ++{ ++} ++ ++int IPASoftBase::init([[maybe_unused]] const IPASettings &settings, ++ const SharedFD &fdStats, ++ const SharedFD &fdParams, ++ const ControlInfoMap &sensorInfoMap) ++{ ++ fdStats_ = std::move(fdStats); ++ if (!fdStats_.isValid()) { ++ LOG(IPASoft, Error) << "Invalid Statistics handle"; ++ return -ENODEV; ++ } ++ ++ fdParams_ = std::move(fdParams); ++ if (!fdParams_.isValid()) { ++ LOG(IPASoft, Error) << "Invalid Parameters handle"; ++ return -ENODEV; ++ } ++ ++ return platformInit(sensorInfoMap); ++} ++ ++int IPASoftBase::configure(const ControlInfoMap &sensorInfoMap) ++{ ++ return platformConfigure(sensorInfoMap); ++} ++ ++int IPASoftBase::start() ++{ ++ return platformStart(); ++} ++ ++void IPASoftBase::stop() ++{ ++ return platformStop(); ++} ++ ++void IPASoftBase::processStats(const ControlList &sensorControls) ++{ ++ return platformProcessStats(sensorControls); ++} ++ ++} /* namespace ipa::soft */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/simple/common/soft_base.h b/src/ipa/simple/common/soft_base.h +new file mode 100644 +index 00000000..85c98702 +--- /dev/null ++++ b/src/ipa/simple/common/soft_base.h +@@ -0,0 +1,50 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * soft-base.h - Software IPA base class ++ */ ++#pragma once ++ ++#include <libcamera/base/shared_fd.h> ++ ++#include <libcamera/controls.h> ++ ++#include <libcamera/ipa/soft_ipa_interface.h> ++ ++namespace libcamera { ++ ++namespace ipa::soft { ++ ++class IPASoftBase : public ipa::soft::IPASoftInterface ++{ ++public: ++ IPASoftBase(); ++ ~IPASoftBase(); ++ ++ int init(const IPASettings &settings, ++ const SharedFD &fdStats, ++ const SharedFD &fdParams, ++ const ControlInfoMap &sensorInfoMap) override; ++ int configure(const ControlInfoMap &sensorInfoMap) override; ++ ++ int start() override; ++ void stop() override; ++ ++ void processStats(const ControlList &sensorControls) override; ++ ++protected: ++ SharedFD fdStats_; ++ SharedFD fdParams_; ++ ++private: ++ virtual int platformInit(const ControlInfoMap &sensorInfoMap) = 0; ++ virtual int platformConfigure(const ControlInfoMap &sensorInfoMap) = 0; ++ virtual int platformStart() = 0; ++ virtual void platformStop() = 0; ++ virtual void platformProcessStats(const ControlList &sensorControls) = 0; ++}; ++ ++} /* namespace ipa::soft */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build +new file mode 100644 +index 00000000..9688bbdb +--- /dev/null ++++ b/src/ipa/simple/meson.build +@@ -0,0 +1,3 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++subdir('common') +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch b/users/flokli/ipu6-softisp/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch new file mode 100644 index 000000000000..b7cb27455b87 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch @@ -0,0 +1,407 @@ +From c0886381a2bbe494b900d699a3858573316059b2 Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Mon, 11 Dec 2023 23:47:47 +0300 +Subject: [PATCH 12/25] libcamera: ipa: Soft IPA: add a Simple Soft IPA + implementation + +Auto exposure/gain and AWB implementation by Dennis, Toon and Martti. + +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Marttico <g.martti@gmail.com> +Signed-off-by: Marttico <g.martti@gmail.com> +Co-authored-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + meson_options.txt | 3 +- + src/ipa/simple/meson.build | 9 + + src/ipa/simple/simple/data/meson.build | 9 + + src/ipa/simple/simple/data/soft.conf | 3 + + src/ipa/simple/simple/meson.build | 26 +++ + src/ipa/simple/simple/soft_simple.cpp | 273 +++++++++++++++++++++++++ + 6 files changed, 322 insertions(+), 1 deletion(-) + create mode 100644 src/ipa/simple/simple/data/meson.build + create mode 100644 src/ipa/simple/simple/data/soft.conf + create mode 100644 src/ipa/simple/simple/meson.build + create mode 100644 src/ipa/simple/simple/soft_simple.cpp + +diff --git a/meson_options.txt b/meson_options.txt +index 5fdc7be8..8ec08658 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -27,7 +27,7 @@ option('gstreamer', + + option('ipas', + type : 'array', +- choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'vimc'], ++ choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple/simple', 'vimc'], + description : 'Select which IPA modules to build') + + option('lc-compliance', +@@ -46,6 +46,7 @@ option('pipelines', + 'rkisp1', + 'rpi/vc4', + 'simple', ++ 'simple/simple', + 'uvcvideo', + 'vimc' + ], +diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build +index 9688bbdb..14be5dc2 100644 +--- a/src/ipa/simple/meson.build ++++ b/src/ipa/simple/meson.build +@@ -1,3 +1,12 @@ + # SPDX-License-Identifier: CC0-1.0 + + subdir('common') ++ ++foreach pipeline : pipelines ++ pipeline = pipeline.split('/') ++ if pipeline.length() < 2 or pipeline[0] != 'simple' ++ continue ++ endif ++ ++ subdir(pipeline[1]) ++endforeach +diff --git a/src/ipa/simple/simple/data/meson.build b/src/ipa/simple/simple/data/meson.build +new file mode 100644 +index 00000000..33548cc6 +--- /dev/null ++++ b/src/ipa/simple/simple/data/meson.build +@@ -0,0 +1,9 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++conf_files = files([ ++ 'soft.conf', ++]) ++ ++install_data(conf_files, ++ install_dir : ipa_data_dir / 'soft', ++ install_tag : 'runtime') +diff --git a/src/ipa/simple/simple/data/soft.conf b/src/ipa/simple/simple/data/soft.conf +new file mode 100644 +index 00000000..0c70e7c0 +--- /dev/null ++++ b/src/ipa/simple/simple/data/soft.conf +@@ -0,0 +1,3 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# Dummy configuration file for the soft IPA. +diff --git a/src/ipa/simple/simple/meson.build b/src/ipa/simple/simple/meson.build +new file mode 100644 +index 00000000..8b5d76b5 +--- /dev/null ++++ b/src/ipa/simple/simple/meson.build +@@ -0,0 +1,26 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++ipa_name = 'ipa_soft_simple' ++ ++mod = shared_module(ipa_name, ++ ['soft_simple.cpp', libcamera_generated_ipa_headers], ++ name_prefix : '', ++ include_directories : [ipa_includes, libipa_includes, '..'], ++ dependencies : libcamera_private, ++ link_with : libipa, ++ link_whole : soft_ipa_common_lib, ++ install : true, ++ install_dir : ipa_install_dir) ++ ++if ipa_sign_module ++ custom_target(ipa_name + '.so.sign', ++ input : mod, ++ output : ipa_name + '.so.sign', ++ command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'], ++ install : false, ++ build_by_default : true) ++endif ++ ++subdir('data') ++ ++ipa_names += ipa_name +diff --git a/src/ipa/simple/simple/soft_simple.cpp b/src/ipa/simple/simple/soft_simple.cpp +new file mode 100644 +index 00000000..93fc1545 +--- /dev/null ++++ b/src/ipa/simple/simple/soft_simple.cpp +@@ -0,0 +1,273 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * soft_simple.cpp - Simple Software Image Processing Algorithm module ++ */ ++ ++#include <sys/mman.h> ++ ++#include <libcamera/base/file.h> ++#include <libcamera/base/log.h> ++ ++#include <libcamera/control_ids.h> ++ ++#include <libcamera/ipa/ipa_interface.h> ++#include <libcamera/ipa/ipa_module_info.h> ++ ++#include "libcamera/internal/camera_sensor.h" ++#include "libcamera/internal/software_isp/debayer_params.h" ++#include "libcamera/internal/software_isp/swisp_stats.h" ++ ++#include "common/soft_base.h" ++ ++#define EXPOSURE_OPTIMAL_VALUE 2.5 ++#define EXPOSURE_SATISFACTORY_OFFSET 0.2 ++ ++namespace libcamera { ++ ++LOG_DECLARE_CATEGORY(IPASoft) ++ ++namespace ipa::soft { ++ ++class IPASoftSimple final : public IPASoftBase ++{ ++public: ++ IPASoftSimple() ++ : IPASoftBase(), ignore_updates_(0) ++ { ++ } ++ ++ ~IPASoftSimple() ++ { ++ if (stats_) ++ munmap(stats_, sizeof(SwIspStats)); ++ if (params_) ++ munmap(params_, sizeof(DebayerParams)); ++ } ++ ++ int platformInit(const ControlInfoMap &sensorInfoMap) override; ++ int platformConfigure(const ControlInfoMap &sensorInfoMap) override; ++ int platformStart() override; ++ void platformStop() override; ++ void platformProcessStats(const ControlList &sensorControls) override; ++ ++private: ++ void update_exposure(double exposuremsv); ++ ++ DebayerParams *params_; ++ SwIspStats *stats_; ++ int exposure_min_, exposure_max_; ++ int again_min_, again_max_; ++ int again_, exposure_; ++ int ignore_updates_; ++}; ++ ++int IPASoftSimple::platformInit(const ControlInfoMap &sensorInfoMap) ++{ ++ params_ = static_cast<DebayerParams *>(mmap(nullptr, sizeof(DebayerParams), ++ PROT_WRITE, MAP_SHARED, ++ fdParams_.get(), 0)); ++ if (!params_) { ++ LOG(IPASoft, Error) << "Unable to map Parameters"; ++ return -ENODEV; ++ } ++ ++ stats_ = static_cast<SwIspStats *>(mmap(nullptr, sizeof(SwIspStats), ++ PROT_READ, MAP_SHARED, ++ fdStats_.get(), 0)); ++ if (!stats_) { ++ LOG(IPASoft, Error) << "Unable to map Statistics"; ++ return -ENODEV; ++ } ++ ++ if (sensorInfoMap.find(V4L2_CID_EXPOSURE) == sensorInfoMap.end()) { ++ LOG(IPASoft, Error) << "Don't have exposure control"; ++ return -EINVAL; ++ } ++ ++ if (sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN) == sensorInfoMap.end()) { ++ LOG(IPASoft, Error) << "Don't have gain control"; ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++int IPASoftSimple::platformConfigure(const ControlInfoMap &sensorInfoMap) ++{ ++ const ControlInfo &exposure_info = sensorInfoMap.find(V4L2_CID_EXPOSURE)->second; ++ const ControlInfo &gain_info = sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN)->second; ++ ++ exposure_min_ = exposure_info.min().get<int>(); ++ if (!exposure_min_) { ++ LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear"; ++ exposure_min_ = 1; ++ } ++ exposure_max_ = exposure_info.max().get<int>(); ++ again_min_ = gain_info.min().get<int>(); ++ if (!again_min_) { ++ LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear"; ++ again_min_ = 100; ++ } ++ again_max_ = gain_info.max().get<int>(); ++ ++ LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_ ++ << ", gain " << again_min_ << "-" << again_max_; ++ ++ return 0; ++} ++ ++int IPASoftSimple::platformStart() ++{ ++ return 0; ++} ++ ++void IPASoftSimple::platformStop() ++{ ++} ++ ++void IPASoftSimple::platformProcessStats(const ControlList &sensorControls) ++{ ++ /* ++ * Calculate red and blue gains for AWB. ++ * Clamp max gain at 4.0, this also avoids 0 division. ++ */ ++ if (stats_->sumR_ <= stats_->sumG_ / 4) ++ params_->gainR = 1024; ++ else ++ params_->gainR = 256 * stats_->sumG_ / stats_->sumR_; ++ ++ if (stats_->sumB_ <= stats_->sumG_ / 4) ++ params_->gainB = 1024; ++ else ++ params_->gainB = 256 * stats_->sumG_ / stats_->sumB_; ++ ++ /* Green gain and gamma values are fixed */ ++ params_->gainG = 256; ++ params_->gamma = 0.5; ++ ++ setIspParams.emit(0); ++ ++ /* ++ * AE / AGC, use 2 frames delay to make sure that the exposure and ++ * the gain set have applied to the camera sensor. ++ */ ++ if (ignore_updates_ > 0) { ++ --ignore_updates_; ++ return; ++ } ++ ++ unsigned int denom = 0; ++ unsigned int num = 0; ++ unsigned int y_histogramSmall[5] = {}; ++ ++ for (int i = 0; i < 16; i++) ++ y_histogramSmall[(i - i / 8) / 3] += stats_->y_histogram[i]; ++ ++ for (int i = 0; i < 5; i++) { ++ LOG(IPASoft, Debug) << i << ": " << y_histogramSmall[i]; ++ denom += y_histogramSmall[i]; ++ num += y_histogramSmall[i] * (i + 1); ++ } ++ ++ float exposuremsv = (float)num / denom; ++ ++ /* sanity check */ ++ if (!sensorControls.contains(V4L2_CID_EXPOSURE) || ++ !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) { ++ LOG(IPASoft, Error) << "Control(s) missing"; ++ return; ++ } ++ ++ ControlList ctrls(sensorControls); ++ ++ exposure_ = ctrls.get(V4L2_CID_EXPOSURE).get<int>(); ++ again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int>(); ++ ++ update_exposure(exposuremsv); ++ ++ ctrls.set(V4L2_CID_EXPOSURE, exposure_); ++ ctrls.set(V4L2_CID_ANALOGUE_GAIN, again_); ++ ++ ignore_updates_ = 2; ++ ++ setSensorControls.emit(ctrls); ++ ++ LOG(IPASoft, Debug) << "exposuremsv " << exposuremsv ++ << " exp " << exposure_ << " again " << again_ ++ << " gain R/B " << params_->gainR << "/" << params_->gainB; ++} ++ ++/* DENOMINATOR of 10 gives ~10% increment/decrement; DENOMINATOR of 5 - about ~20% */ ++#define DENOMINATOR 10 ++#define UP_NUMERATOR (DENOMINATOR + 1) ++#define DOWN_NUMERATOR (DENOMINATOR - 1) ++ ++void IPASoftSimple::update_exposure(double exposuremsv) ++{ ++ int next; ++ ++ if (exposuremsv < EXPOSURE_OPTIMAL_VALUE - EXPOSURE_SATISFACTORY_OFFSET) { ++ next = exposure_ * UP_NUMERATOR / DENOMINATOR; ++ if (next - exposure_ < 1) ++ exposure_ += 1; ++ else ++ exposure_ = next; ++ if (exposure_ >= exposure_max_) { ++ next = again_ * UP_NUMERATOR / DENOMINATOR; ++ if (next - again_ < 1) ++ again_ += 1; ++ else ++ again_ = next; ++ } ++ } ++ ++ if (exposuremsv > EXPOSURE_OPTIMAL_VALUE + EXPOSURE_SATISFACTORY_OFFSET) { ++ if (exposure_ == exposure_max_ && again_ != again_min_) { ++ next = again_ * DOWN_NUMERATOR / DENOMINATOR; ++ if (again_ - next < 1) ++ again_ -= 1; ++ else ++ again_ = next; ++ } else { ++ next = exposure_ * DOWN_NUMERATOR / DENOMINATOR; ++ if (exposure_ - next < 1) ++ exposure_ -= 1; ++ else ++ exposure_ = next; ++ } ++ } ++ ++ if (exposure_ > exposure_max_) ++ exposure_ = exposure_max_; ++ else if (exposure_ < exposure_min_) ++ exposure_ = exposure_min_; ++ ++ if (again_ > again_max_) ++ again_ = again_max_; ++ else if (again_ < again_min_) ++ again_ = again_min_; ++} ++ ++} /* namespace ipa::soft */ ++ ++/* ++ * External IPA module interface ++ */ ++extern "C" { ++const struct IPAModuleInfo ipaModuleInfo = { ++ IPA_MODULE_API_VERSION, ++ 0, ++ "SimplePipelineHandler", ++ "soft/simple", ++}; ++ ++IPAInterface *ipaCreate() ++{ ++ return new ipa::soft::IPASoftSimple(); ++} ++ ++} /* extern "C" */ ++ ++} /* namespace libcamera */ +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch b/users/flokli/ipu6-softisp/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch new file mode 100644 index 000000000000..24636ed7569a --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch @@ -0,0 +1,483 @@ +From 21f1dd954a44b4e8f81abbfea276e4b60f8a8297 Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Thu, 23 Nov 2023 16:47:15 +0300 +Subject: [PATCH 13/25] libcamera: software_isp: add Simple SoftwareIsp + implementation + +The implementation of SoftwareIsp handles creation of Soft IPA +and interactions with it, so that the pipeline handler wouldn't +need to care about the Soft IPA. + +Doxygen documentation by Dennis Bonke. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Hans de Goede <hdegoede@redhat.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + .../internal/software_isp/meson.build | 1 + + .../internal/software_isp/swisp_simple.h | 163 ++++++++++++ + src/libcamera/software_isp/meson.build | 19 ++ + src/libcamera/software_isp/swisp_simple.cpp | 238 ++++++++++++++++++ + 4 files changed, 421 insertions(+) + create mode 100644 include/libcamera/internal/software_isp/swisp_simple.h + create mode 100644 src/libcamera/software_isp/swisp_simple.cpp + +diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build +index b5a0d737..cf21ace5 100644 +--- a/include/libcamera/internal/software_isp/meson.build ++++ b/include/libcamera/internal/software_isp/meson.build +@@ -4,6 +4,7 @@ libcamera_internal_headers += files([ + 'debayer.h', + 'debayer_cpu.h', + 'debayer_params.h', ++ 'swisp_simple.h', + 'swisp_stats.h', + 'swstats.h', + 'swstats_cpu.h', +diff --git a/include/libcamera/internal/software_isp/swisp_simple.h b/include/libcamera/internal/software_isp/swisp_simple.h +new file mode 100644 +index 00000000..87613c23 +--- /dev/null ++++ b/include/libcamera/internal/software_isp/swisp_simple.h +@@ -0,0 +1,163 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * swisp_simple.h - Simple software ISP implementation ++ */ ++ ++#pragma once ++ ++#include <libcamera/base/log.h> ++#include <libcamera/base/thread.h> ++ ++#include <libcamera/pixel_format.h> ++ ++#include <libcamera/ipa/soft_ipa_interface.h> ++#include <libcamera/ipa/soft_ipa_proxy.h> ++ ++#include "libcamera/internal/dma_heaps.h" ++#include "libcamera/internal/software_isp.h" ++#include "libcamera/internal/software_isp/debayer_cpu.h" ++ ++namespace libcamera { ++ ++/** ++ * \brief Class for the Simple Software ISP. ++ * ++ * Implementation of the SoftwareIsp interface. ++ */ ++class SwIspSimple : public SoftwareIsp ++{ ++public: ++ /** ++ * \brief Constructor for the SwIspSimple object. ++ * ++ * \param[in] pipe The pipeline handler in use. ++ * \param[in] sensorControls The sensor controls. ++ */ ++ SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls); ++ ~SwIspSimple() {} ++ ++ /** ++ * \brief Load a configuration from a file. ++ * \param[in] filename The file to load from. ++ * ++ * \return 0 on success. ++ */ ++ int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } ++ ++ /** ++ * \brief Gets if there is a valid debayer object. ++ * ++ * \returns true if there is, false otherwise. ++ */ ++ bool isValid() const; ++ ++ /** ++ * \brief Get the supported output formats. ++ * \param[in] input The input format. ++ * ++ * \return all supported output formats or an empty vector if there are none. ++ */ ++ std::vector<PixelFormat> formats(PixelFormat input); ++ ++ /** ++ * \brief Get the supported output sizes for the given input format and size. ++ * \param[in] inputFormat The input format. ++ * \param[in] inputSize The input size. ++ * ++ * \return The valid size ranges or an empty range if there are none. ++ */ ++ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize); ++ ++ /** ++ * \brief Get the stride and the frame size. ++ * \param[in] outputFormat The output format. ++ * \param[in] size The output size. ++ * ++ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config. ++ */ ++ std::tuple<unsigned int, unsigned int> ++ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); ++ ++ /** ++ * \brief Configure the SwIspSimple object according to the passed in parameters. ++ * \param[in] inputCfg The input configuration. ++ * \param[in] outputCfgs The output configurations. ++ * \param[in] sensorControls The sensor controls. ++ * ++ * \return 0 on success, a negative errno on failure. ++ */ ++ int configure(const StreamConfiguration &inputCfg, ++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, ++ const ControlInfoMap &sensorControls); ++ ++ /** ++ * \brief Exports the buffers for use in processing. ++ * \param[in] output The number of outputs requested. ++ * \param[in] count The number of planes. ++ * \param[out] buffers The exported buffers. ++ * ++ * \return count when successful, a negative return value if an error occurred. ++ */ ++ int exportBuffers(unsigned int output, unsigned int count, ++ std::vector<std::unique_ptr<FrameBuffer>> *buffers); ++ ++ /** ++ * \brief Process the statistics gathered. ++ * \param[in] sensorControls The sensor controls. ++ */ ++ void processStats(const ControlList &sensorControls); ++ ++ /** ++ * \brief Starts the Software ISP worker. ++ * ++ * \return 0 on success, any other value indicates an error. ++ */ ++ int start(); ++ ++ /** ++ * \brief Stops the Software ISP worker. ++ */ ++ void stop(); ++ ++ /** ++ * \brief Queues buffers for processing. ++ * \param[in] input The input framebuffer. ++ * \param[in] outputs The output framebuffers. ++ * ++ * \return 0 on success, a negative errno on failure ++ */ ++ int queueBuffers(FrameBuffer *input, ++ const std::map<unsigned int, FrameBuffer *> &outputs); ++ ++ /** ++ * \brief Get the signal for when the sensor controls are set. ++ * ++ * \return The control list of the sensor controls. ++ */ ++ Signal<const ControlList &> &getSignalSetSensorControls(); ++ ++ /** ++ * \brief Process the input framebuffer. ++ * \param[in] input The input framebuffer. ++ * \param[out] output The output framebuffer. ++ */ ++ void process(FrameBuffer *input, FrameBuffer *output); ++ ++private: ++ void saveIspParams(int dummy); ++ void statsReady(int dummy); ++ void inputReady(FrameBuffer *input); ++ void outputReady(FrameBuffer *output); ++ ++ std::unique_ptr<DebayerCpu> debayer_; ++ Thread ispWorkerThread_; ++ SharedMemObject<DebayerParams> sharedParams_; ++ DebayerParams debayerParams_; ++ DmaHeap dmaHeap_; ++ ++ std::unique_ptr<ipa::soft::IPAProxySoft> ipa_; ++}; ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build +index 6d7a44d7..9464f2fd 100644 +--- a/src/libcamera/software_isp/meson.build ++++ b/src/libcamera/software_isp/meson.build +@@ -6,3 +6,22 @@ libcamera_sources += files([ + 'swstats.cpp', + 'swstats_cpu.cpp', + ]) ++ ++# Software ISP is enabled for 'simple' pipeline handler. ++# The 'pipelines' option value should be ++# 'simple/<Software ISP implementation>' e.g.: ++# -Dpipelines=simple/simple ++# The source file should be named swisp_<Software ISP implementation>.cpp, ++# e.g. 'swisp_simple.cpp'. ++ ++foreach pipeline : pipelines ++ pipeline = pipeline.split('/') ++ if pipeline.length() == 2 and pipeline[0] == 'simple' ++ libcamera_sources += files([ ++ 'swisp_' + pipeline[1] + '.cpp', ++ ]) ++ # the 'break' below can be removed if/when multiple ++ # Software ISP implementations are allowed in single build ++ break ++ endif ++endforeach +diff --git a/src/libcamera/software_isp/swisp_simple.cpp b/src/libcamera/software_isp/swisp_simple.cpp +new file mode 100644 +index 00000000..0884166e +--- /dev/null ++++ b/src/libcamera/software_isp/swisp_simple.cpp +@@ -0,0 +1,238 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2023, Linaro Ltd ++ * ++ * swisp_simple.cpp - Simple software ISP implementation ++ */ ++ ++#include "libcamera/internal/software_isp/swisp_simple.h" ++ ++#include <sys/mman.h> ++#include <sys/types.h> ++#include <unistd.h> ++ ++#include <libcamera/formats.h> ++#include <libcamera/stream.h> ++ ++#include "libcamera/internal/bayer_format.h" ++#include "libcamera/internal/framebuffer.h" ++#include "libcamera/internal/ipa_manager.h" ++#include "libcamera/internal/mapped_framebuffer.h" ++ ++namespace libcamera { ++ ++SwIspSimple::SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls) ++ : SoftwareIsp(pipe, sensorControls), debayer_(nullptr), debayerParams_{ 256, 256, 256, 0.5f }, ++ dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System) ++{ ++ if (!dmaHeap_.isValid()) { ++ LOG(SoftwareIsp, Error) << "Failed to create DmaHeap object"; ++ return; ++ } ++ ++ sharedParams_ = SharedMemObject<DebayerParams>("softIsp_params"); ++ if (!sharedParams_.fd().isValid()) { ++ LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters"; ++ return; ++ } ++ ++ std::unique_ptr<SwStatsCpu> stats; ++ ++ stats = std::make_unique<SwStatsCpu>(); ++ if (!stats) { ++ LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object"; ++ return; ++ } ++ ++ stats->statsReady.connect(this, &SwIspSimple::statsReady); ++ ++ debayer_ = std::make_unique<DebayerCpu>(std::move(stats)); ++ if (!debayer_) { ++ LOG(SoftwareIsp, Error) << "Failed to create DebayerCpu object"; ++ return; ++ } ++ ++ debayer_->inputBufferReady.connect(this, &SwIspSimple::inputReady); ++ debayer_->outputBufferReady.connect(this, &SwIspSimple::outputReady); ++ ++ ipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe, 0, 0); ++ if (!ipa_) { ++ LOG(SoftwareIsp, Error) ++ << "Creating IPA for software ISP failed"; ++ debayer_.reset(); ++ return; ++ } ++ ++ int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" }, ++ debayer_->getStatsFD(), ++ sharedParams_.fd(), ++ sensorControls); ++ if (ret) { ++ LOG(SoftwareIsp, Error) << "IPA init failed"; ++ debayer_.reset(); ++ return; ++ } ++ ++ ipa_->setIspParams.connect(this, &SwIspSimple::saveIspParams); ++ ++ debayer_->moveToThread(&ispWorkerThread_); ++} ++ ++void SwIspSimple::processStats(const ControlList &sensorControls) ++{ ++ ASSERT(ipa_); ++ ipa_->processStats(sensorControls); ++} ++ ++Signal<const ControlList &> &SwIspSimple::getSignalSetSensorControls() ++{ ++ ASSERT(ipa_); ++ return ipa_->setSensorControls; ++} ++ ++bool SwIspSimple::isValid() const ++{ ++ return !!debayer_; ++} ++ ++std::vector<PixelFormat> SwIspSimple::formats(PixelFormat inputFormat) ++{ ++ ASSERT(debayer_ != nullptr); ++ ++ return debayer_->formats(inputFormat); ++} ++ ++SizeRange SwIspSimple::sizes(PixelFormat inputFormat, const Size &inputSize) ++{ ++ ASSERT(debayer_ != nullptr); ++ ++ return debayer_->sizes(inputFormat, inputSize); ++} ++ ++std::tuple<unsigned int, unsigned int> ++SwIspSimple::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) ++{ ++ ASSERT(debayer_ != nullptr); ++ ++ return debayer_->strideAndFrameSize(outputFormat, size); ++} ++ ++int SwIspSimple::configure(const StreamConfiguration &inputCfg, ++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, ++ const ControlInfoMap &sensorControls) ++{ ++ ASSERT(ipa_ != nullptr && debayer_ != nullptr); ++ ++ int ret = ipa_->configure(sensorControls); ++ if (ret < 0) ++ return ret; ++ ++ return debayer_->configure(inputCfg, outputCfgs); ++} ++ ++int SwIspSimple::exportBuffers(unsigned int output, unsigned int count, ++ std::vector<std::unique_ptr<FrameBuffer>> *buffers) ++{ ++ ASSERT(debayer_ != nullptr); ++ ++ /* single output for now */ ++ if (output >= 1) ++ return -EINVAL; ++ ++ for (unsigned int i = 0; i < count; i++) { ++ const std::string name = "frame-" + std::to_string(i); ++ const size_t frameSize = debayer_->frameSize(); ++ ++ FrameBuffer::Plane outPlane; ++ outPlane.fd = SharedFD(dmaHeap_.alloc(name.c_str(), frameSize)); ++ if (!outPlane.fd.isValid()) { ++ LOG(SoftwareIsp, Error) ++ << "failed to allocate a dma_buf"; ++ return -ENOMEM; ++ } ++ outPlane.offset = 0; ++ outPlane.length = frameSize; ++ ++ std::vector<FrameBuffer::Plane> planes{ outPlane }; ++ buffers->emplace_back(std::make_unique<FrameBuffer>(std::move(planes))); ++ } ++ ++ return count; ++} ++ ++int SwIspSimple::queueBuffers(FrameBuffer *input, ++ const std::map<unsigned int, FrameBuffer *> &outputs) ++{ ++ unsigned int mask = 0; ++ ++ /* ++ * Validate the outputs as a sanity check: at least one output is ++ * required, all outputs must reference a valid stream and no two ++ * outputs can reference the same stream. ++ */ ++ if (outputs.empty()) ++ return -EINVAL; ++ ++ for (auto [index, buffer] : outputs) { ++ if (!buffer) ++ return -EINVAL; ++ if (index >= 1) /* only single stream atm */ ++ return -EINVAL; ++ if (mask & (1 << index)) ++ return -EINVAL; ++ ++ mask |= 1 << index; ++ } ++ ++ process(input, outputs.at(0)); ++ ++ return 0; ++} ++ ++int SwIspSimple::start() ++{ ++ int ret = ipa_->start(); ++ if (ret) ++ return ret; ++ ++ ispWorkerThread_.start(); ++ return 0; ++} ++ ++void SwIspSimple::stop() ++{ ++ ispWorkerThread_.exit(); ++ ispWorkerThread_.wait(); ++ ++ ipa_->stop(); ++} ++ ++void SwIspSimple::process(FrameBuffer *input, FrameBuffer *output) ++{ ++ debayer_->invokeMethod(&DebayerCpu::process, ++ ConnectionTypeQueued, input, output, debayerParams_); ++} ++ ++void SwIspSimple::saveIspParams([[maybe_unused]] int dummy) ++{ ++ debayerParams_ = *sharedParams_; ++} ++ ++void SwIspSimple::statsReady(int dummy) ++{ ++ ispStatsReady.emit(dummy); ++} ++ ++void SwIspSimple::inputReady(FrameBuffer *input) ++{ ++ inputBufferReady.emit(input); ++} ++ ++void SwIspSimple::outputReady(FrameBuffer *output) ++{ ++ outputBufferReady.emit(output); ++} ++ ++REGISTER_SOFTWAREISP(SwIspSimple) ++ ++} /* namespace libcamera */ +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch b/users/flokli/ipu6-softisp/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch new file mode 100644 index 000000000000..fc603fc9c8eb --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch @@ -0,0 +1,238 @@ +From 155065b3d78c14173fd71c21fe0639f94c3da109 Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Fri, 1 Dec 2023 15:45:14 +0300 +Subject: [PATCH 14/25] libcamera: pipeline: simple: rename converterBuffers_ + and related vars + +The converterBuffers_ and the converterQueue_ are not that specific +to the Converter, and could be used by another entity doing the format +conversion. + +Rename converterBuffers_, converterQueue_, and useConverter_ to +conversionBuffers_, conversionQueue_ and useConversion_ to +disassociate them from the Converter. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + src/libcamera/pipeline/simple/simple.cpp | 63 ++++++++++++------------ + 1 file changed, 32 insertions(+), 31 deletions(-) + +diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp +index 4d0e7255..fa298746 100644 +--- a/src/libcamera/pipeline/simple/simple.cpp ++++ b/src/libcamera/pipeline/simple/simple.cpp +@@ -269,17 +269,18 @@ public: + std::vector<Configuration> configs_; + std::map<PixelFormat, std::vector<const Configuration *>> formats_; + ++ std::vector<std::unique_ptr<FrameBuffer>> conversionBuffers_; ++ std::queue<std::map<unsigned int, FrameBuffer *>> conversionQueue_; ++ bool useConversion_; ++ + std::unique_ptr<Converter> converter_; +- std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_; +- bool useConverter_; +- std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_; + + private: + void tryPipeline(unsigned int code, const Size &size); + static std::vector<const MediaPad *> routedSourcePads(MediaPad *sink); + +- void converterInputDone(FrameBuffer *buffer); +- void converterOutputDone(FrameBuffer *buffer); ++ void conversionInputDone(FrameBuffer *buffer); ++ void conversionOutputDone(FrameBuffer *buffer); + }; + + class SimpleCameraConfiguration : public CameraConfiguration +@@ -503,8 +504,8 @@ int SimpleCameraData::init() + << "Failed to create converter, disabling format conversion"; + converter_.reset(); + } else { +- converter_->inputBufferReady.connect(this, &SimpleCameraData::converterInputDone); +- converter_->outputBufferReady.connect(this, &SimpleCameraData::converterOutputDone); ++ converter_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone); ++ converter_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone); + } + } + +@@ -740,7 +741,7 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + * point converting an erroneous buffer. + */ + if (buffer->metadata().status != FrameMetadata::FrameSuccess) { +- if (!useConverter_) { ++ if (!useConversion_) { + /* No conversion, just complete the request. */ + Request *request = buffer->request(); + pipe->completeBuffer(request, buffer); +@@ -756,16 +757,16 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + if (buffer->metadata().status != FrameMetadata::FrameCancelled) + video_->queueBuffer(buffer); + +- if (converterQueue_.empty()) ++ if (conversionQueue_.empty()) + return; + + Request *request = nullptr; +- for (auto &item : converterQueue_.front()) { ++ for (auto &item : conversionQueue_.front()) { + FrameBuffer *outputBuffer = item.second; + request = outputBuffer->request(); + pipe->completeBuffer(request, outputBuffer); + } +- converterQueue_.pop(); ++ conversionQueue_.pop(); + + if (request) + pipe->completeRequest(request); +@@ -782,9 +783,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + */ + Request *request = buffer->request(); + +- if (useConverter_ && !converterQueue_.empty()) { ++ if (useConversion_ && !conversionQueue_.empty()) { + const std::map<unsigned int, FrameBuffer *> &outputs = +- converterQueue_.front(); ++ conversionQueue_.front(); + if (!outputs.empty()) { + FrameBuffer *outputBuffer = outputs.begin()->second; + if (outputBuffer) +@@ -801,14 +802,14 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + * conversion is needed. If there's no queued request, just requeue the + * captured buffer for capture. + */ +- if (useConverter_) { +- if (converterQueue_.empty()) { ++ if (useConversion_) { ++ if (conversionQueue_.empty()) { + video_->queueBuffer(buffer); + return; + } + +- converter_->queueBuffers(buffer, converterQueue_.front()); +- converterQueue_.pop(); ++ converter_->queueBuffers(buffer, conversionQueue_.front()); ++ conversionQueue_.pop(); + return; + } + +@@ -817,13 +818,13 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + pipe->completeRequest(request); + } + +-void SimpleCameraData::converterInputDone(FrameBuffer *buffer) ++void SimpleCameraData::conversionInputDone(FrameBuffer *buffer) + { + /* Queue the input buffer back for capture. */ + video_->queueBuffer(buffer); + } + +-void SimpleCameraData::converterOutputDone(FrameBuffer *buffer) ++void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer) + { + SimplePipelineHandler *pipe = SimpleCameraData::pipe(); + +@@ -1159,14 +1160,14 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) + + /* Configure the converter if needed. */ + std::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs; +- data->useConverter_ = config->needConversion(); ++ data->useConversion_ = config->needConversion(); + + for (unsigned int i = 0; i < config->size(); ++i) { + StreamConfiguration &cfg = config->at(i); + + cfg.setStream(&data->streams_[i]); + +- if (data->useConverter_) ++ if (data->useConversion_) + outputCfgs.push_back(cfg); + } + +@@ -1192,7 +1193,7 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, + * Export buffers on the converter or capture video node, depending on + * whether the converter is used or not. + */ +- if (data->useConverter_) ++ if (data->useConversion_) + return data->converter_->exportBuffers(data->streamIndex(stream), + count, buffers); + else +@@ -1213,13 +1214,13 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL + return -EBUSY; + } + +- if (data->useConverter_) { ++ if (data->useConversion_) { + /* + * When using the converter allocate a fixed number of internal + * buffers. + */ + ret = video->allocateBuffers(kNumInternalBuffers, +- &data->converterBuffers_); ++ &data->conversionBuffers_); + } else { + /* Otherwise, prepare for using buffers from the only stream. */ + Stream *stream = &data->streams_[0]; +@@ -1238,7 +1239,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL + return ret; + } + +- if (data->useConverter_) { ++ if (data->useConversion_) { + ret = data->converter_->start(); + if (ret < 0) { + stop(camera); +@@ -1246,7 +1247,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL + } + + /* Queue all internal buffers for capture. */ +- for (std::unique_ptr<FrameBuffer> &buffer : data->converterBuffers_) ++ for (std::unique_ptr<FrameBuffer> &buffer : data->conversionBuffers_) + video->queueBuffer(buffer.get()); + } + +@@ -1258,7 +1259,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera) + SimpleCameraData *data = cameraData(camera); + V4L2VideoDevice *video = data->video_; + +- if (data->useConverter_) ++ if (data->useConversion_) + data->converter_->stop(); + + video->streamOff(); +@@ -1266,7 +1267,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera) + + video->bufferReady.disconnect(data, &SimpleCameraData::bufferReady); + +- data->converterBuffers_.clear(); ++ data->conversionBuffers_.clear(); + + releasePipeline(data); + } +@@ -1284,7 +1285,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) + * queue, it will be handed to the converter in the capture + * completion handler. + */ +- if (data->useConverter_) { ++ if (data->useConversion_) { + buffers.emplace(data->streamIndex(stream), buffer); + } else { + ret = data->video_->queueBuffer(buffer); +@@ -1293,8 +1294,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) + } + } + +- if (data->useConverter_) +- data->converterQueue_.push(std::move(buffers)); ++ if (data->useConversion_) ++ data->conversionQueue_.push(std::move(buffers)); + + return 0; + } +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch b/users/flokli/ipu6-softisp/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch new file mode 100644 index 000000000000..f639a3b74c93 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch @@ -0,0 +1,243 @@ +From ad61052d9aea1f1af6aaef9ce7e5d447c595187c Mon Sep 17 00:00:00 2001 +From: Andrey Konovalov <andrey.konovalov@linaro.org> +Date: Tue, 12 Dec 2023 02:02:37 +0300 +Subject: [PATCH 15/25] libcamera: pipeline: simple: enable use of Soft ISP and + Soft IPA + +To enable the Simple Soft ISP and Soft IPA for simple pipeline handler +configure the build with: + -Dpipelines=simple/simple -Dipas=simple/simple + +If the pipeline uses Converter, Soft ISP and Soft IPA aren't +available. + +Configuring the build with just: + -Dpipelines=simple +makes it to work like before - Soft ISP and Soft IPA aren't used. + +Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + src/libcamera/pipeline/simple/simple.cpp | 111 ++++++++++++++++++----- + 1 file changed, 89 insertions(+), 22 deletions(-) + +diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp +index fa298746..c76510c2 100644 +--- a/src/libcamera/pipeline/simple/simple.cpp ++++ b/src/libcamera/pipeline/simple/simple.cpp +@@ -34,6 +34,7 @@ + #include "libcamera/internal/device_enumerator.h" + #include "libcamera/internal/media_device.h" + #include "libcamera/internal/pipeline_handler.h" ++#include "libcamera/internal/software_isp.h" + #include "libcamera/internal/v4l2_subdevice.h" + #include "libcamera/internal/v4l2_videodevice.h" + +@@ -274,6 +275,7 @@ public: + bool useConversion_; + + std::unique_ptr<Converter> converter_; ++ std::unique_ptr<SoftwareIsp> swIsp_; + + private: + void tryPipeline(unsigned int code, const Size &size); +@@ -281,6 +283,9 @@ private: + + void conversionInputDone(FrameBuffer *buffer); + void conversionOutputDone(FrameBuffer *buffer); ++ ++ void ispStatsReady(int dummy); ++ void setSensorControls(const ControlList &sensorControls); + }; + + class SimpleCameraConfiguration : public CameraConfiguration +@@ -509,6 +514,25 @@ int SimpleCameraData::init() + } + } + ++ /* ++ * Create SoftwareIsp unconditionally if no converter is used ++ * - to be revisited ++ */ ++ if (!converter_) { ++ swIsp_ = SoftwareIspFactoryBase::create(pipe, sensor_->controls()); ++ if (!swIsp_) { ++ LOG(SimplePipeline, Warning) ++ << "Failed to create software ISP, disabling software debayering"; ++ swIsp_.reset(); ++ } else { ++ swIsp_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone); ++ swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone); ++ swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady); ++ ++ swIsp_->getSignalSetSensorControls().connect(this, &SimpleCameraData::setSensorControls); ++ } ++ } ++ + video_ = pipe->video(entities_.back().entity); + ASSERT(video_); + +@@ -599,12 +623,20 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size) + config.captureFormat = pixelFormat; + config.captureSize = format.size; + +- if (!converter_) { +- config.outputFormats = { pixelFormat }; +- config.outputSizes = config.captureSize; +- } else { ++ if (converter_) { + config.outputFormats = converter_->formats(pixelFormat); + config.outputSizes = converter_->sizes(format.size); ++ } else if (swIsp_) { ++ config.outputFormats = swIsp_->formats(pixelFormat); ++ config.outputSizes = swIsp_->sizes(pixelFormat, format.size); ++ if (config.outputFormats.empty()) { ++ /* Do not use swIsp for unsupported pixelFormat's */ ++ config.outputFormats = { pixelFormat }; ++ config.outputSizes = config.captureSize; ++ } ++ } else { ++ config.outputFormats = { pixelFormat }; ++ config.outputSizes = config.captureSize; + } + + configs_.push_back(config); +@@ -750,9 +782,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + } + + /* +- * The converter is in use. Requeue the internal buffer for +- * capture (unless the stream is being stopped), and complete +- * the request with all the user-facing buffers. ++ * The converter or Software ISP is in use. Requeue the internal ++ * buffer for capture (unless the stream is being stopped), and ++ * complete the request with all the user-facing buffers. + */ + if (buffer->metadata().status != FrameMetadata::FrameCancelled) + video_->queueBuffer(buffer); +@@ -798,9 +830,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + buffer->metadata().timestamp); + + /* +- * Queue the captured and the request buffer to the converter if format +- * conversion is needed. If there's no queued request, just requeue the +- * captured buffer for capture. ++ * Queue the captured and the request buffer to the converter or Software ++ * ISP if format conversion is needed. If there's no queued request, just ++ * requeue the captured buffer for capture. + */ + if (useConversion_) { + if (conversionQueue_.empty()) { +@@ -808,7 +840,11 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) + return; + } + +- converter_->queueBuffers(buffer, conversionQueue_.front()); ++ if (converter_) ++ converter_->queueBuffers(buffer, conversionQueue_.front()); ++ else ++ swIsp_->queueBuffers(buffer, conversionQueue_.front()); ++ + conversionQueue_.pop(); + return; + } +@@ -834,6 +870,18 @@ void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer) + pipe->completeRequest(request); + } + ++void SimpleCameraData::ispStatsReady([[maybe_unused]] int dummy) ++{ ++ swIsp_->processStats(sensor_->getControls({ V4L2_CID_ANALOGUE_GAIN, ++ V4L2_CID_EXPOSURE })); ++} ++ ++void SimpleCameraData::setSensorControls(const ControlList &sensorControls) ++{ ++ ControlList ctrls(sensorControls); ++ sensor_->setControls(&ctrls); ++} ++ + /* Retrieve all source pads connected to a sink pad through active routes. */ + std::vector<const MediaPad *> SimpleCameraData::routedSourcePads(MediaPad *sink) + { +@@ -1016,8 +1064,10 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() + /* Set the stride, frameSize and bufferCount. */ + if (needConversion_) { + std::tie(cfg.stride, cfg.frameSize) = +- data_->converter_->strideAndFrameSize(cfg.pixelFormat, +- cfg.size); ++ (data_->converter_) ? data_->converter_->strideAndFrameSize(cfg.pixelFormat, ++ cfg.size) ++ : data_->swIsp_->strideAndFrameSize(cfg.pixelFormat, ++ cfg.size); + if (cfg.stride == 0) + return Invalid; + } else { +@@ -1180,7 +1230,9 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) + inputCfg.stride = captureFormat.planes[0].bpl; + inputCfg.bufferCount = kNumInternalBuffers; + +- return data->converter_->configure(inputCfg, outputCfgs); ++ return (data->converter_) ? data->converter_->configure(inputCfg, outputCfgs) ++ : data->swIsp_->configure(inputCfg, outputCfgs, ++ data->sensor_->controls()); + } + + int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, +@@ -1194,8 +1246,10 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, + * whether the converter is used or not. + */ + if (data->useConversion_) +- return data->converter_->exportBuffers(data->streamIndex(stream), +- count, buffers); ++ return (data->converter_) ? data->converter_->exportBuffers(data->streamIndex(stream), ++ count, buffers) ++ : data->swIsp_->exportBuffers(data->streamIndex(stream), ++ count, buffers); + else + return data->video_->exportBuffers(count, buffers); + } +@@ -1240,10 +1294,18 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL + } + + if (data->useConversion_) { +- ret = data->converter_->start(); +- if (ret < 0) { +- stop(camera); +- return ret; ++ if (data->converter_) { ++ ret = data->converter_->start(); ++ if (ret < 0) { ++ stop(camera); ++ return ret; ++ } ++ } else if (data->swIsp_) { ++ ret = data->swIsp_->start(); ++ if (ret < 0) { ++ stop(camera); ++ return ret; ++ } + } + + /* Queue all internal buffers for capture. */ +@@ -1259,8 +1321,13 @@ void SimplePipelineHandler::stopDevice(Camera *camera) + SimpleCameraData *data = cameraData(camera); + V4L2VideoDevice *video = data->video_; + +- if (data->useConversion_) +- data->converter_->stop(); ++ if (data->useConversion_) { ++ if (data->converter_) ++ data->converter_->stop(); ++ else if (data->swIsp_) { ++ data->swIsp_->stop(); ++ } ++ } + + video->streamOff(); + video->releaseBuffers(); +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch b/users/flokli/ipu6-softisp/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch new file mode 100644 index 000000000000..04388859a543 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch @@ -0,0 +1,199 @@ +From 66ef9f32e67a96655d10ba38f7a830b3bbfe50f2 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Thu, 14 Dec 2023 19:09:44 +0100 +Subject: [PATCH 16/25] libcamera: swstats_cpu: Add support for 8, 10 and 12 + bpp unpacked bayer input + +Add support for 8, 10 and 12 bpp unpacked bayer input for all 4 standard +bayer orders. + +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../internal/software_isp/swstats_cpu.h | 9 ++ + src/libcamera/software_isp/swstats_cpu.cpp | 126 ++++++++++++++++++ + 2 files changed, 135 insertions(+) + +diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h +index 8bb86e98..e7abc6bb 100644 +--- a/include/libcamera/internal/software_isp/swstats_cpu.h ++++ b/include/libcamera/internal/software_isp/swstats_cpu.h +@@ -11,6 +11,7 @@ + + #pragma once + ++#include "libcamera/internal/bayer_format.h" + #include "libcamera/internal/shared_mem_object.h" + #include "libcamera/internal/software_isp/swisp_stats.h" + #include "libcamera/internal/software_isp/swstats.h" +@@ -31,6 +32,14 @@ public: + const SharedFD &getStatsFD() { return sharedStats_.fd(); } + int configure(const StreamConfiguration &inputCfg); + private: ++ int setupStandardBayerOrder(BayerFormat::Order order); ++ /* Bayer 8 bpp unpacked */ ++ void statsBGGR8Line0(const uint8_t *src[]); ++ /* Bayer 10 bpp unpacked */ ++ void statsBGGR10Line0(const uint8_t *src[]); ++ /* Bayer 12 bpp unpacked */ ++ void statsBGGR12Line0(const uint8_t *src[]); ++ /* Bayer 10 bpp packed */ + void statsBGGR10PLine0(const uint8_t *src[]); + void statsGBRG10PLine0(const uint8_t *src[]); + void resetStats(void); +diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp +index 59453d07..87550371 100644 +--- a/src/libcamera/software_isp/swstats_cpu.cpp ++++ b/src/libcamera/software_isp/swstats_cpu.cpp +@@ -59,6 +59,83 @@ static const unsigned int BLUE_Y_MUL = 29; /* 0.11 * 256 */ + stats_.sumG_ += sumG; \ + stats_.sumB_ += sumB; + ++void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[]) ++{ ++ const uint8_t *src0 = src[1] + window_.x; ++ const uint8_t *src1 = src[2] + window_.x; ++ ++ SWISP_LINARO_START_LINE_STATS(uint8_t) ++ ++ if (swap_lines_) ++ std::swap(src0, src1); ++ ++ /* x += 4 sample every other 2x2 block */ ++ for (int x = 0; x < (int)window_.width; x += 4) { ++ b = src0[x]; ++ g = src0[x + 1]; ++ g2 = src1[x]; ++ r = src1[x + 1]; ++ ++ g = (g + g2) / 2; ++ ++ SWISP_LINARO_ACCUMULATE_LINE_STATS(1) ++ } ++ ++ SWISP_LINARO_FINISH_LINE_STATS() ++} ++ ++void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[]) ++{ ++ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; ++ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; ++ ++ SWISP_LINARO_START_LINE_STATS(uint16_t) ++ ++ if (swap_lines_) ++ std::swap(src0, src1); ++ ++ /* x += 4 sample every other 2x2 block */ ++ for (int x = 0; x < (int)window_.width; x += 4) { ++ b = src0[x]; ++ g = src0[x + 1]; ++ g2 = src1[x]; ++ r = src1[x + 1]; ++ ++ g = (g + g2) / 2; ++ ++ /* divide Y by 4 for 10 -> 8 bpp value */ ++ SWISP_LINARO_ACCUMULATE_LINE_STATS(4) ++ } ++ ++ SWISP_LINARO_FINISH_LINE_STATS() ++} ++ ++void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[]) ++{ ++ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; ++ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; ++ ++ SWISP_LINARO_START_LINE_STATS(uint16_t) ++ ++ if (swap_lines_) ++ std::swap(src0, src1); ++ ++ /* x += 4 sample every other 2x2 block */ ++ for (int x = 0; x < (int)window_.width; x += 4) { ++ b = src0[x]; ++ g = src0[x + 1]; ++ g2 = src1[x]; ++ r = src1[x + 1]; ++ ++ g = (g + g2) / 2; ++ ++ /* divide Y by 16 for 12 -> 8 bpp value */ ++ SWISP_LINARO_ACCUMULATE_LINE_STATS(16) ++ } ++ ++ SWISP_LINARO_FINISH_LINE_STATS() ++} ++ + static inline __attribute__((always_inline)) void + statsBayer10P(const int width, const uint8_t *src0, const uint8_t *src1, bool bggr, SwIspStats &stats_) + { +@@ -124,6 +201,39 @@ void SwStatsCpu::finishStats(void) + statsReady.emit(0); + } + ++/* ++ * Check if order is a standard Bayer order and setup x_shift_ and swap_lines_ ++ * so that a single BGGR stats function can be used for all 4 standard orders. ++ */ ++int SwStatsCpu::setupStandardBayerOrder(BayerFormat::Order order) ++{ ++ switch (order) { ++ case BayerFormat::BGGR: ++ x_shift_ = 0; ++ swap_lines_ = false; ++ break; ++ case BayerFormat::GBRG: ++ x_shift_ = 1; /* BGGR -> GBRG */ ++ swap_lines_ = false; ++ break; ++ case BayerFormat::GRBG: ++ x_shift_ = 0; ++ swap_lines_ = true; /* BGGR -> GRBG */ ++ break; ++ case BayerFormat::RGGB: ++ x_shift_ = 1; /* BGGR -> GBRG */ ++ swap_lines_ = true; /* GBRG -> RGGB */ ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ patternSize_.height = 2; ++ patternSize_.width = 2; ++ y_skip_mask_ = 0x02; /* Skip every 3th and 4th line */ ++ return 0; ++} ++ + int SwStatsCpu::configure(const StreamConfiguration &inputCfg) + { + BayerFormat bayerFormat = +@@ -132,6 +242,22 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg) + startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats; + finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats; + ++ if (bayerFormat.packing == BayerFormat::Packing::None && ++ setupStandardBayerOrder(bayerFormat.order) == 0) { ++ bpp_ = (bayerFormat.bitDepth + 7) & ~7; ++ switch (bayerFormat.bitDepth) { ++ case 8: ++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR8Line0; ++ return 0; ++ case 10: ++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR10Line0; ++ return 0; ++ case 12: ++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR12Line0; ++ return 0; ++ } ++ } ++ + if (bayerFormat.bitDepth == 10 && + bayerFormat.packing == BayerFormat::Packing::CSI2) { + bpp_ = 10; +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch b/users/flokli/ipu6-softisp/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch new file mode 100644 index 000000000000..a5a992f8764d --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch @@ -0,0 +1,237 @@ +From b7b211eb56d98d5b170bd73a23b55aeb45bde8c5 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Thu, 14 Dec 2023 19:57:15 +0100 +Subject: [PATCH 17/25] libcamera: debayer_cpu: Add support for 8, 10 and 12 + bpp unpacked bayer input + +Add support for 8, 10 and 12 bpp unpacked bayer input for all 4 standard +bayer orders. + +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../internal/software_isp/debayer_cpu.h | 13 ++ + src/libcamera/software_isp/debayer_cpu.cpp | 128 ++++++++++++++++++ + 2 files changed, 141 insertions(+) + +diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h +index 78573f44..1147b368 100644 +--- a/include/libcamera/internal/software_isp/debayer_cpu.h ++++ b/include/libcamera/internal/software_isp/debayer_cpu.h +@@ -17,6 +17,7 @@ + + #include <libcamera/base/object.h> + ++#include "libcamera/internal/bayer_format.h" + #include "libcamera/internal/software_isp/swstats_cpu.h" + #include "libcamera/internal/software_isp/debayer.h" + +@@ -75,11 +76,21 @@ public: + * \return The output frame size. + */ + unsigned int frameSize() { return outputConfig_.frameSize; } ++ + private: + void initLinePointers(const uint8_t *linePointers[], const uint8_t *src); + void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src); + void process2(const uint8_t *src, uint8_t *dst); + void process4(const uint8_t *src, uint8_t *dst); ++ /* 8-bit raw bayer format */ ++ void debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); ++ void debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); ++ /* unpacked 10-bit raw bayer format */ ++ void debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); ++ void debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); ++ /* unpacked 12-bit raw bayer format */ ++ void debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); ++ void debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */ + void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); + void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); +@@ -103,6 +114,7 @@ private: + + int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); + int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config); ++ int setupStandardBayerOrder(BayerFormat::Order order); + int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat); + + uint8_t gamma_[1024]; +@@ -119,6 +131,7 @@ private: + std::unique_ptr<SwStatsCpu> stats_; + uint8_t *lineBuffers_[5]; + unsigned int lineBufferIndex_; ++ unsigned int x_shift_; /* Offset of 0/1 applied to window_.x */ + bool enableInputMemcpy_; + float gamma_correction_; + int measuredFrames_; +diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp +index e0c3c658..7b7623b7 100644 +--- a/src/libcamera/software_isp/debayer_cpu.cpp ++++ b/src/libcamera/software_isp/debayer_cpu.cpp +@@ -45,6 +45,11 @@ DebayerCpu::~DebayerCpu() + free(lineBuffers_[i]); + } + ++#define DECLARE_SRC_POINTERS(pixel_t) \ ++ const pixel_t *prev = (const pixel_t *)src[0] + x_shift_; \ ++ const pixel_t *curr = (const pixel_t *)src[1] + x_shift_; \ ++ const pixel_t *next = (const pixel_t *)src[2] + x_shift_; ++ + // RGR + // GBG + // RGR +@@ -81,6 +86,70 @@ DebayerCpu::~DebayerCpu() + *dst++ = red_[curr[x] / (div)]; \ + x++; + ++void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ DECLARE_SRC_POINTERS(uint8_t) ++ ++ for (int x = 0; x < (int)window_.width;) { ++ BGGR_BGR888(1, 1, 1) ++ GBRG_BGR888(1, 1, 1) ++ } ++} ++ ++void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ DECLARE_SRC_POINTERS(uint8_t) ++ ++ for (int x = 0; x < (int)window_.width;) { ++ GRBG_BGR888(1, 1, 1) ++ RGGB_BGR888(1, 1, 1) ++ } ++} ++ ++void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ DECLARE_SRC_POINTERS(uint16_t) ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* divide values by 4 for 10 -> 8 bpp value */ ++ BGGR_BGR888(1, 1, 4) ++ GBRG_BGR888(1, 1, 4) ++ } ++} ++ ++void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ DECLARE_SRC_POINTERS(uint16_t) ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* divide values by 4 for 10 -> 8 bpp value */ ++ GRBG_BGR888(1, 1, 4) ++ RGGB_BGR888(1, 1, 4) ++ } ++} ++ ++void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ DECLARE_SRC_POINTERS(uint16_t) ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* divide values by 16 for 12 -> 8 bpp value */ ++ BGGR_BGR888(1, 1, 16) ++ GBRG_BGR888(1, 1, 16) ++ } ++} ++ ++void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) ++{ ++ DECLARE_SRC_POINTERS(uint16_t) ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* divide values by 16 for 12 -> 8 bpp value */ ++ GRBG_BGR888(1, 1, 16) ++ RGGB_BGR888(1, 1, 16) ++ } ++} ++ + void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) + { + const int width_in_bytes = window_.width * 5 / 4; +@@ -170,6 +239,16 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputFormat); + ++ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && ++ bayerFormat.packing == BayerFormat::Packing::None && ++ isStandardBayerOrder(bayerFormat.order)) { ++ config.bpp = (bayerFormat.bitDepth + 7) & ~7; ++ config.patternSize.width = 2; ++ config.patternSize.height = 2; ++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 }); ++ return 0; ++ } ++ + if (bayerFormat.bitDepth == 10 && + bayerFormat.packing == BayerFormat::Packing::CSI2 && + isStandardBayerOrder(bayerFormat.order)) { +@@ -197,12 +276,61 @@ int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &c + return -EINVAL; + } + ++/* ++ * Check for standard Bayer orders and set x_shift_ and swap debayer0/1, so that ++ * a single pair of BGGR debayer functions can be used for all 4 standard orders. ++ */ ++int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order) ++{ ++ switch (order) { ++ case BayerFormat::BGGR: ++ break; ++ case BayerFormat::GBRG: ++ x_shift_ = 1; /* BGGR -> GBRG */ ++ break; ++ case BayerFormat::GRBG: ++ std::swap(debayer0_, debayer1_); /* BGGR -> GRBG */ ++ break; ++ case BayerFormat::RGGB: ++ x_shift_ = 1; /* BGGR -> GBRG */ ++ std::swap(debayer0_, debayer1_); /* GBRG -> RGGB */ ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + /* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */ + int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat) + { + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputFormat); + ++ x_shift_ = 0; ++ ++ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && ++ bayerFormat.packing == BayerFormat::Packing::None && ++ isStandardBayerOrder(bayerFormat.order)) { ++ switch (bayerFormat.bitDepth) { ++ case 8: ++ debayer0_ = &DebayerCpu::debayer8_BGBG_BGR888; ++ debayer1_ = &DebayerCpu::debayer8_GRGR_BGR888; ++ break; ++ case 10: ++ debayer0_ = &DebayerCpu::debayer10_BGBG_BGR888; ++ debayer1_ = &DebayerCpu::debayer10_GRGR_BGR888; ++ break; ++ case 12: ++ debayer0_ = &DebayerCpu::debayer12_BGBG_BGR888; ++ debayer1_ = &DebayerCpu::debayer12_GRGR_BGR888; ++ break; ++ } ++ setupStandardBayerOrder(bayerFormat.order); ++ return 0; ++ } ++ + if (bayerFormat.bitDepth == 10 && + bayerFormat.packing == BayerFormat::Packing::CSI2) { + switch (bayerFormat.order) { +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch b/users/flokli/ipu6-softisp/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch new file mode 100644 index 000000000000..50d826be7a48 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch @@ -0,0 +1,125 @@ +From b835b2c90785ee02bc98888bf165713d16c24cc4 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Mon, 18 Dec 2023 19:21:07 +0100 +Subject: [PATCH 18/25] libcamera: debayer_cpu: Add BGR888 output support + +BGR888 is RGB888 with the red and blue pixels swapped, adjust +the debayering to swap the red and blue pixels in the bayer pattern +to add support for writing formats::BGR888. + +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s +Tested-by: Pavel Machek <pavel@ucw.cz> +--- + .../internal/software_isp/debayer_cpu.h | 1 + + src/libcamera/software_isp/debayer_cpu.cpp | 43 ++++++++++++++++--- + 2 files changed, 39 insertions(+), 5 deletions(-) + +diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h +index 1147b368..bdeab7c0 100644 +--- a/include/libcamera/internal/software_isp/debayer_cpu.h ++++ b/include/libcamera/internal/software_isp/debayer_cpu.h +@@ -133,6 +133,7 @@ private: + unsigned int lineBufferIndex_; + unsigned int x_shift_; /* Offset of 0/1 applied to window_.x */ + bool enableInputMemcpy_; ++ bool swapRedBlueGains_; + float gamma_correction_; + int measuredFrames_; + int64_t frameProcessTime_; +diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp +index 7b7623b7..0edea4d3 100644 +--- a/src/libcamera/software_isp/debayer_cpu.cpp ++++ b/src/libcamera/software_isp/debayer_cpu.cpp +@@ -245,7 +245,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + config.bpp = (bayerFormat.bitDepth + 7) & ~7; + config.patternSize.width = 2; + config.patternSize.height = 2; +- config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 }); ++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 }); + return 0; + } + +@@ -255,7 +255,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + config.bpp = 10; + config.patternSize.width = 4; /* 5 bytes per *4* pixels */ + config.patternSize.height = 2; +- config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 }); ++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 }); + return 0; + } + +@@ -266,7 +266,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + + int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config) + { +- if (outputFormat == formats::RGB888) { ++ if (outputFormat == formats::RGB888 || outputFormat == formats::BGR888) { + config.bpp = 24; + return 0; + } +@@ -302,12 +302,41 @@ int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order) + return 0; + } + +-/* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */ +-int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat) ++int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat) + { + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputFormat); + ++ swapRedBlueGains_ = false; ++ ++ switch (outputFormat) { ++ case formats::RGB888: ++ break; ++ case formats::BGR888: ++ /* Swap R and B in bayer order to generate BGR888 instead of RGB888 */ ++ swapRedBlueGains_ = true; ++ ++ switch (bayerFormat.order) { ++ case BayerFormat::BGGR: ++ bayerFormat.order = BayerFormat::RGGB; ++ break; ++ case BayerFormat::GBRG: ++ bayerFormat.order = BayerFormat::GRBG; ++ break; ++ case BayerFormat::GRBG: ++ bayerFormat.order = BayerFormat::GBRG; ++ break; ++ case BayerFormat::RGGB: ++ bayerFormat.order = BayerFormat::BGGR; ++ break; ++ default: ++ goto invalid_fmt; ++ } ++ break; ++ default: ++ goto invalid_fmt; ++ } ++ + x_shift_ = 0; + + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && +@@ -355,6 +384,7 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] Pi + } + } + ++invalid_fmt: + LOG(Debayer, Error) << "Unsupported input output format combination"; + return -EINVAL; + } +@@ -594,6 +624,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams + gamma_correction_ = params.gamma; + } + ++ if (swapRedBlueGains_) ++ std::swap(params.gainR, params.gainB); ++ + for (int i = 0; i < 256; i++) { + int idx; + +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch b/users/flokli/ipu6-softisp/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch new file mode 100644 index 000000000000..cdbd0b992250 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch @@ -0,0 +1,30 @@ +From eb45bdfe66af7844a779bc6fcf923cd951336309 Mon Sep 17 00:00:00 2001 +From: Dennis Bonke <admin@dennisbonke.com> +Date: Fri, 6 Oct 2023 10:39:45 +0200 +Subject: [PATCH 19/25] libcamera: pipeline: simple: Enable simplepipeline for + intel-ipu6 DNU + +Do Not Upstream, first the ipu6 CSI receiver code needs to land in +the kernel. + +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + src/libcamera/pipeline/simple/simple.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp +index c76510c2..130843cd 100644 +--- a/src/libcamera/pipeline/simple/simple.cpp ++++ b/src/libcamera/pipeline/simple/simple.cpp +@@ -197,6 +197,7 @@ static const SimplePipelineInfo supportedDevices[] = { + { "mxc-isi", {} }, + { "qcom-camss", {} }, + { "sun6i-csi", {} }, ++ { "intel-ipu6", {} }, + }; + + } /* namespace */ +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch b/users/flokli/ipu6-softisp/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch new file mode 100644 index 000000000000..768ec07119e8 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch @@ -0,0 +1,237 @@ +From e03beabbad83c4c283c7f1c2c4798b6c3e2eaf06 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Tue, 19 Dec 2023 11:16:26 +0100 +Subject: [PATCH 20/25] libcamera: Add support for IGIG_GBGR_IGIG_GRGB bayer + order DNU + +The ov01a1s sensor has the following bayer pattern (4x4 tile repeating): + +IGIG +GBGR +IGIG +GRGB + +Add support for this PixelFormat to libcamera. + +Do Not Upstream, first the include/linux/media-bus-format.h and +include/linux/videodev2.h changes need to land in the upstream kernel. + +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + include/libcamera/internal/bayer_format.h | 3 ++- + include/linux/drm_fourcc.h | 2 ++ + include/linux/media-bus-format.h | 4 +++- + include/linux/videodev2.h | 3 +++ + src/libcamera/bayer_format.cpp | 5 +++++ + src/libcamera/camera_sensor.cpp | 3 +++ + src/libcamera/formats.cpp | 20 ++++++++++++++++++++ + src/libcamera/formats.yaml | 5 +++++ + src/libcamera/v4l2_pixelformat.cpp | 4 ++++ + src/libcamera/v4l2_subdevice.cpp | 1 + + 10 files changed, 48 insertions(+), 2 deletions(-) + +diff --git a/include/libcamera/internal/bayer_format.h b/include/libcamera/internal/bayer_format.h +index 78ba3969..e77106c3 100644 +--- a/include/libcamera/internal/bayer_format.h ++++ b/include/libcamera/internal/bayer_format.h +@@ -27,7 +27,8 @@ public: + GBRG = 1, + GRBG = 2, + RGGB = 3, +- MONO = 4 ++ MONO = 4, ++ IGIG_GBGR_IGIG_GRGB = 5, + }; + + enum class Packing : uint16_t { +diff --git a/include/linux/drm_fourcc.h b/include/linux/drm_fourcc.h +index 1496e097..750ae8c9 100644 +--- a/include/linux/drm_fourcc.h ++++ b/include/linux/drm_fourcc.h +@@ -405,6 +405,8 @@ extern "C" { + #define DRM_FORMAT_SGRBG10 fourcc_code('B', 'A', '1', '0') + #define DRM_FORMAT_SGBRG10 fourcc_code('G', 'B', '1', '0') + #define DRM_FORMAT_SBGGR10 fourcc_code('B', 'G', '1', '0') ++/* Mixed 10 bit bayer + ir pixel pattern found on Omnivision ov01a1s */ ++#define DRM_FORMAT_SIGIG_GBGR_IGIG_GRGB10 fourcc_code('O', 'V', '1', 'S') + + /* 12-bit Bayer formats */ + #define DRM_FORMAT_SRGGB12 fourcc_code('R', 'G', '1', '2') +diff --git a/include/linux/media-bus-format.h b/include/linux/media-bus-format.h +index 0dfc11ee..c5fbda0e 100644 +--- a/include/linux/media-bus-format.h ++++ b/include/linux/media-bus-format.h +@@ -112,7 +112,7 @@ + #define MEDIA_BUS_FMT_YUV16_1X48 0x202a + #define MEDIA_BUS_FMT_UYYVYY16_0_5X48 0x202b + +-/* Bayer - next is 0x3021 */ ++/* Bayer - next is 0x3022 */ + #define MEDIA_BUS_FMT_SBGGR8_1X8 0x3001 + #define MEDIA_BUS_FMT_SGBRG8_1X8 0x3013 + #define MEDIA_BUS_FMT_SGRBG8_1X8 0x3002 +@@ -145,6 +145,8 @@ + #define MEDIA_BUS_FMT_SGBRG16_1X16 0x301e + #define MEDIA_BUS_FMT_SGRBG16_1X16 0x301f + #define MEDIA_BUS_FMT_SRGGB16_1X16 0x3020 ++/* Mixed bayer + ir pixel pattern found on ov01a1s */ ++#define MEDIA_BUS_FMT_SIGIG_GBGR_IGIG_GRGB10_1X10 0x3021 + + /* JPEG compressed formats - next is 0x4002 */ + #define MEDIA_BUS_FMT_JPEG_1X8 0x4001 +diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h +index bfb315d6..13c6c9d3 100644 +--- a/include/linux/videodev2.h ++++ b/include/linux/videodev2.h +@@ -678,6 +678,9 @@ struct v4l2_pix_format { + #define V4L2_PIX_FMT_SGBRG16 v4l2_fourcc('G', 'B', '1', '6') /* 16 GBGB.. RGRG.. */ + #define V4L2_PIX_FMT_SGRBG16 v4l2_fourcc('G', 'R', '1', '6') /* 16 GRGR.. BGBG.. */ + #define V4L2_PIX_FMT_SRGGB16 v4l2_fourcc('R', 'G', '1', '6') /* 16 RGRG.. GBGB.. */ ++ /* 10bit mixed bayer + ir pixel pattern found on ov01a1s */ ++#define V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10 v4l2_fourcc('O', 'V', '1', 'S') /* unpacked */ ++#define V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P v4l2_fourcc('O', 'V', '1', 'P') /* packed */ + + /* HSV formats */ + #define V4L2_PIX_FMT_HSV24 v4l2_fourcc('H', 'S', 'V', '3') +diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp +index 3bf15fb4..ae227540 100644 +--- a/src/libcamera/bayer_format.cpp ++++ b/src/libcamera/bayer_format.cpp +@@ -108,6 +108,8 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{ + { formats::SGRBG10, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10) } }, + { { BayerFormat::RGGB, 10, BayerFormat::Packing::None }, + { formats::SRGGB10, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10) } }, ++ { { BayerFormat::IGIG_GBGR_IGIG_GRGB, 10, BayerFormat::Packing::None }, ++ { formats::SIGIG_GBGR_IGIG_GRGB10, V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10) } }, + { { BayerFormat::BGGR, 10, BayerFormat::Packing::CSI2 }, + { formats::SBGGR10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P) } }, + { { BayerFormat::GBRG, 10, BayerFormat::Packing::CSI2 }, +@@ -116,6 +118,8 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{ + { formats::SGRBG10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10P) } }, + { { BayerFormat::RGGB, 10, BayerFormat::Packing::CSI2 }, + { formats::SRGGB10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P) } }, ++ { { BayerFormat::IGIG_GBGR_IGIG_GRGB, 10, BayerFormat::Packing::CSI2 }, ++ { formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P) } }, + { { BayerFormat::BGGR, 10, BayerFormat::Packing::IPU3 }, + { formats::SBGGR10_IPU3, V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SBGGR10) } }, + { { BayerFormat::GBRG, 10, BayerFormat::Packing::IPU3 }, +@@ -193,6 +197,7 @@ const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{ + { MEDIA_BUS_FMT_SGBRG10_1X10, { BayerFormat::GBRG, 10, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, { BayerFormat::GRBG, 10, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SRGGB10_1X10, { BayerFormat::RGGB, 10, BayerFormat::Packing::None } }, ++ { MEDIA_BUS_FMT_SIGIG_GBGR_IGIG_GRGB10_1X10, { BayerFormat::IGIG_GBGR_IGIG_GRGB, 10, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SBGGR12_1X12, { BayerFormat::BGGR, 12, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SGBRG12_1X12, { BayerFormat::GBRG, 12, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SGRBG12_1X12, { BayerFormat::GRBG, 12, BayerFormat::Packing::None } }, +diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/camera_sensor.cpp +index 0ef78d9c..f19f72ea 100644 +--- a/src/libcamera/camera_sensor.cpp ++++ b/src/libcamera/camera_sensor.cpp +@@ -510,6 +510,9 @@ int CameraSensor::initProperties() + case BayerFormat::MONO: + cfa = properties::draft::MONO; + break; ++ case BayerFormat::IGIG_GBGR_IGIG_GRGB: ++ cfa = properties::draft::RGB; ++ break; + } + + properties_.set(properties::draft::ColorFilterArrangement, cfa); +diff --git a/src/libcamera/formats.cpp b/src/libcamera/formats.cpp +index 447e6238..aef7d598 100644 +--- a/src/libcamera/formats.cpp ++++ b/src/libcamera/formats.cpp +@@ -599,6 +599,16 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ + .pixelsPerGroup = 2, + .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, ++ { formats::SIGIG_GBGR_IGIG_GRGB10, { ++ .name = "SIGIG_GBGR_IGIG_GRGB10", ++ .format = formats::SIGIG_GBGR_IGIG_GRGB10, ++ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10), }, ++ .bitsPerPixel = 10, ++ .colourEncoding = PixelFormatInfo::ColourEncodingRAW, ++ .packed = false, ++ .pixelsPerGroup = 4, ++ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }}, ++ } }, + { formats::SBGGR10_CSI2P, { + .name = "SBGGR10_CSI2P", + .format = formats::SBGGR10_CSI2P, +@@ -639,6 +649,16 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ + .pixelsPerGroup = 4, + .planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, ++ { formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P, { ++ .name = "SIGIG_GBGR_IGIG_GRGB10_CSI2P", ++ .format = formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P, ++ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P), }, ++ .bitsPerPixel = 10, ++ .colourEncoding = PixelFormatInfo::ColourEncodingRAW, ++ .packed = true, ++ .pixelsPerGroup = 4, ++ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }}, ++ } }, + { formats::SBGGR12, { + .name = "SBGGR12", + .format = formats::SBGGR12, +diff --git a/src/libcamera/formats.yaml b/src/libcamera/formats.yaml +index 539ac0b3..0786a900 100644 +--- a/src/libcamera/formats.yaml ++++ b/src/libcamera/formats.yaml +@@ -100,6 +100,8 @@ formats: + fourcc: DRM_FORMAT_SGBRG10 + - SBGGR10: + fourcc: DRM_FORMAT_SBGGR10 ++ - SIGIG_GBGR_IGIG_GRGB10: ++ fourcc: DRM_FORMAT_SIGIG_GBGR_IGIG_GRGB10 + + - SRGGB12: + fourcc: DRM_FORMAT_SRGGB12 +@@ -144,6 +146,9 @@ formats: + - SBGGR10_CSI2P: + fourcc: DRM_FORMAT_SBGGR10 + mod: MIPI_FORMAT_MOD_CSI2_PACKED ++ - SIGIG_GBGR_IGIG_GRGB10_CSI2P: ++ fourcc: DRM_FORMAT_SIGIG_GBGR_IGIG_GRGB10 ++ mod: MIPI_FORMAT_MOD_CSI2_PACKED + + - SRGGB12_CSI2P: + fourcc: DRM_FORMAT_SRGGB12 +diff --git a/src/libcamera/v4l2_pixelformat.cpp b/src/libcamera/v4l2_pixelformat.cpp +index 5551c62e..53078d99 100644 +--- a/src/libcamera/v4l2_pixelformat.cpp ++++ b/src/libcamera/v4l2_pixelformat.cpp +@@ -153,6 +153,8 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{ + { formats::SGRBG10, "10-bit Bayer GRGR/BGBG" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10), + { formats::SRGGB10, "10-bit Bayer RGRG/GBGB" } }, ++ { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10), ++ { formats::SIGIG_GBGR_IGIG_GRGB10, "10-bit Bayer GRGB/IGIG/GBGR/IGIG" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P), + { formats::SBGGR10_CSI2P, "10-bit Bayer BGBG/GRGR Packed" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10P), +@@ -161,6 +163,8 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{ + { formats::SGRBG10_CSI2P, "10-bit Bayer GRGR/BGBG Packed" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P), + { formats::SRGGB10_CSI2P, "10-bit Bayer RGRG/GBGB Packed" } }, ++ { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P), ++ { formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P, "10-bit Bayer GRGB/IGIG/GBGR/IGIG Packed" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12), + { formats::SBGGR12, "12-bit Bayer BGBG/GRGR" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12), +diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp +index 15e8206a..4ad37aaf 100644 +--- a/src/libcamera/v4l2_subdevice.cpp ++++ b/src/libcamera/v4l2_subdevice.cpp +@@ -128,6 +128,7 @@ const std::map<uint32_t, V4L2SubdeviceFormatInfo> formatInfoMap = { + { MEDIA_BUS_FMT_SGBRG10_1X10, { 10, "SGBRG10_1X10", PixelFormatInfo::ColourEncodingRAW } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, { 10, "SGRBG10_1X10", PixelFormatInfo::ColourEncodingRAW } }, + { MEDIA_BUS_FMT_SRGGB10_1X10, { 10, "SRGGB10_1X10", PixelFormatInfo::ColourEncodingRAW } }, ++ { MEDIA_BUS_FMT_SIGIG_GBGR_IGIG_GRGB10_1X10, { 10, "SIGIG_GBGR_IGIG_GRGB10_1X10", PixelFormatInfo::ColourEncodingRAW } }, + { MEDIA_BUS_FMT_SBGGR12_1X12, { 12, "SBGGR12_1X12", PixelFormatInfo::ColourEncodingRAW } }, + { MEDIA_BUS_FMT_SGBRG12_1X12, { 12, "SGBRG12_1X12", PixelFormatInfo::ColourEncodingRAW } }, + { MEDIA_BUS_FMT_SGRBG12_1X12, { 12, "SGRBG12_1X12", PixelFormatInfo::ColourEncodingRAW } }, +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch b/users/flokli/ipu6-softisp/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch new file mode 100644 index 000000000000..44985a94e16f --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch @@ -0,0 +1,131 @@ +From f939e68a3ef556e572f0140df6d7ef17d72f457e Mon Sep 17 00:00:00 2001 +From: Marttico <g.martti@gmail.com> +Date: Wed, 20 Dec 2023 20:26:15 +0100 +Subject: [PATCH 21/25] libcamera: swstats_cpu: Add support for 10bpp + IGIG_GBGR_IGIG_GRGB input + +Add support to SwStatsCpu for 10bpp IGIG_GBGR_IGIG_GRGB input +generated by the Omnivision ov01a1s sensor. + +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Marttico <g.martti@gmail.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../internal/software_isp/swstats_cpu.h | 3 + + src/libcamera/software_isp/swstats_cpu.cpp | 76 +++++++++++++++++++ + 2 files changed, 79 insertions(+) + +diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h +index e7abc6bb..a47241e1 100644 +--- a/include/libcamera/internal/software_isp/swstats_cpu.h ++++ b/include/libcamera/internal/software_isp/swstats_cpu.h +@@ -42,6 +42,9 @@ private: + /* Bayer 10 bpp packed */ + void statsBGGR10PLine0(const uint8_t *src[]); + void statsGBRG10PLine0(const uint8_t *src[]); ++ /* IGIG_GBGR_IGIG_GRGB 10 bpp unpacked */ ++ void statsRGBIR10Line0(const uint8_t *src[]); ++ void statsRGBIR10Line2(const uint8_t *src[]); + void resetStats(void); + void finishStats(void); + +diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp +index 87550371..96e21be5 100644 +--- a/src/libcamera/software_isp/swstats_cpu.cpp ++++ b/src/libcamera/software_isp/swstats_cpu.cpp +@@ -187,6 +187,68 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[]) + statsBayer10P(window_.width, src0, src1, false, stats_); + } + ++void SwStatsCpu::statsRGBIR10Line0(const uint8_t *src[]) ++{ ++ const uint16_t *src0_16 = (const uint16_t *)src[2] + window_.x; ++ const uint16_t *src1_16 = (const uint16_t *)src[3] + window_.x; ++ uint16_t g3, g4; ++ ++ SWISP_LINARO_START_LINE_STATS(uint16_t) ++ ++ /* x += 8 sample every other 4x4 block */ ++ for (int x = 0; x < (int)window_.width; x += 8) { ++ /* IGIG */ ++ //i = src0_16[x]; ++ g2 = src0_16[x + 1]; ++ //i = src0_16[x + 2]; ++ g4 = src0_16[x + 3]; ++ ++ /* GBGR */ ++ g = src1_16[x]; ++ b = src1_16[x + 1]; ++ g3 = src1_16[x + 2]; ++ r = src1_16[x + 3]; ++ ++ g = (g + g2 + g3 + g4) / 4; ++ ++ /* divide Y by 4 for 10 -> 8 bpp value */ ++ SWISP_LINARO_ACCUMULATE_LINE_STATS(4) ++ } ++ ++ SWISP_LINARO_FINISH_LINE_STATS() ++} ++ ++void SwStatsCpu::statsRGBIR10Line2(const uint8_t *src[]) ++{ ++ const uint16_t *src0_16 = (const uint16_t *)src[2] + window_.x; ++ const uint16_t *src1_16 = (const uint16_t *)src[3] + window_.x; ++ uint16_t g3, g4; ++ ++ SWISP_LINARO_START_LINE_STATS(uint16_t) ++ ++ /* x += 8 sample every other 4x4 block */ ++ for (int x = 0; x < (int)window_.width; x += 8) { ++ /* IGIG */ ++ //i = src0_16[x]; ++ g2 = src0_16[x + 1]; ++ //i = src0_16[x + 2]; ++ g4 = src0_16[x + 3]; ++ ++ /* GRGB */ ++ g = src1_16[x]; ++ r = src1_16[x + 1]; ++ g3 = src1_16[x + 2]; ++ b = src1_16[x + 3]; ++ ++ g = (g + g2 + g3 + g4) / 4; ++ ++ /* divide Y by 4 for 10 -> 8 bpp value */ ++ SWISP_LINARO_ACCUMULATE_LINE_STATS(4) ++ } ++ ++ SWISP_LINARO_FINISH_LINE_STATS() ++} ++ + void SwStatsCpu::resetStats(void) + { + stats_.sumR_ = 0; +@@ -282,6 +344,20 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg) + } + } + ++ if (bayerFormat.bitDepth == 10 && ++ bayerFormat.packing == BayerFormat::Packing::None && ++ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) { ++ bpp_ = 16; ++ patternSize_.height = 4; ++ patternSize_.width = 4; ++ y_skip_mask_ = 0x04; ++ x_shift_ = 0; ++ swap_lines_ = false; ++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsRGBIR10Line0; ++ stats2_ = (SwStats::statsProcessFn)&SwStatsCpu::statsRGBIR10Line2; ++ return 0; ++ } ++ + LOG(SwStats, Info) + << "Unsupported input format " << inputCfg.pixelFormat.toString(); + return -EINVAL; +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch b/users/flokli/ipu6-softisp/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch new file mode 100644 index 000000000000..4054a9563f35 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch @@ -0,0 +1,315 @@ +From e3638943a8bd3f93b8d81c3996035c60755b97f6 Mon Sep 17 00:00:00 2001 +From: Marttico <g.martti@gmail.com> +Date: Wed, 20 Dec 2023 20:28:12 +0100 +Subject: [PATCH 22/25] libcamera: debayer_cpu: Add support for 10bpp + IGIG_GBGR_IGIG_GRGB input + +Add support to DebayerCpu for 10bpp IGIG_GBGR_IGIG_GRGB input +generated by the Omnivision ov01a1s sensor. + +Co-authored-by: Dennis Bonke <admin@dennisbonke.com> +Signed-off-by: Dennis Bonke <admin@dennisbonke.com> +Co-authored-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Toon Langendam <t.langendam@gmail.com> +Signed-off-by: Marttico <g.martti@gmail.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../internal/software_isp/debayer_cpu.h | 5 + + src/libcamera/software_isp/debayer_cpu.cpp | 251 ++++++++++++++++++ + 2 files changed, 256 insertions(+) + +diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h +index bdeab7c0..52af117f 100644 +--- a/include/libcamera/internal/software_isp/debayer_cpu.h ++++ b/include/libcamera/internal/software_isp/debayer_cpu.h +@@ -96,6 +96,11 @@ private: + void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); + void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); + void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); ++ /* IGIG_GBGR_IGIG_GRGB unpacked 10-bit raw bayer format */ ++ void debayerIGIG10Line0(uint8_t *dst, const uint8_t *src[]); ++ void debayerGBGR10Line1(uint8_t *dst, const uint8_t *src[]); ++ void debayerIGIG10Line2(uint8_t *dst, const uint8_t *src[]); ++ void debayerGRGB10Line3(uint8_t *dst, const uint8_t *src[]); + + typedef void (DebayerCpu::*debayerFn)(uint8_t *dst, const uint8_t *src[]); + +diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp +index 0edea4d3..41c8805f 100644 +--- a/src/libcamera/software_isp/debayer_cpu.cpp ++++ b/src/libcamera/software_isp/debayer_cpu.cpp +@@ -228,6 +228,238 @@ void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]) + } + } + ++void DebayerCpu::debayerIGIG10Line0(uint8_t *dst, const uint8_t *src[]) ++{ ++ const uint16_t *prev = (const uint16_t *)src[1]; ++ const uint16_t *curr = (const uint16_t *)src[2]; ++ const uint16_t *next = (const uint16_t *)src[3]; ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* ++ * IGIG line pixel 0: IGIGI ++ * GBGRG ++ * IGIGI ++ * GRGBG ++ * IGIGI ++ */ ++ *dst++ = blue_[(prev[x - 1] + next[x + 1]) / 8]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[(prev[x + 1] + next[x - 1]) / 8]; ++ x++; ++ ++ /* ++ * IGIG line pixel 1: GIGIG ++ * BGRGB ++ * GIGIG ++ * RGBGR ++ * GIGIG ++ */ ++ *dst++ = blue_[next[x] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[prev[x] / 4]; ++ x++; ++ ++ /* ++ * IGIG line pixel 2: IGIGI ++ * GRGBG ++ * IGIGI ++ * GBGRG ++ * IGIGI ++ */ ++ *dst++ = blue_[(prev[x + 1] + next[x - 1]) / 8]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[(prev[x - 1] + next[x + 1]) / 8]; ++ x++; ++ ++ /* ++ * IGIG line pixel 3: GIGIG ++ * RGBGR ++ * GIGIG ++ * BGRGB ++ * GIGIG ++ */ ++ *dst++ = blue_[prev[x] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[next[x] / 4]; ++ x++; ++ } ++} ++ ++void DebayerCpu::debayerGBGR10Line1(uint8_t *dst, const uint8_t *src[]) ++{ ++ const uint16_t *prev2 = (const uint16_t *)src[0]; ++ const uint16_t *prev = (const uint16_t *)src[1]; ++ const uint16_t *curr = (const uint16_t *)src[2]; ++ const uint16_t *next = (const uint16_t *)src[3]; ++ const uint16_t *next2 = (const uint16_t *)src[4]; ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* ++ * GBGR line pixel 0: GBGRG ++ * IGIGI ++ * GRGBG ++ * IGIGI ++ * GBGRG ++ */ ++ *dst++ = blue_[curr[x + 1] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[curr[x - 1] / 4]; ++ x++; ++ ++ /* ++ * GBGR line pixel 1: BGRGB ++ * GIGIG ++ * RGBGR ++ * GIGIG ++ * BGRGB ++ */ ++ *dst++ = blue_[curr[x] / 4]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; ++ x++; ++ ++ /* ++ * GBGR line pixel 2: GRGBG ++ * IGIGI ++ * GBGRG ++ * IGIGI ++ * GRGBG ++ */ ++ *dst++ = blue_[curr[x - 1] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[curr[x + 1] / 4]; ++ x++; ++ ++ /* ++ * GBGR line pixel 3: RGBGR ++ * GIGIG ++ * BGRGB ++ * GIGIG ++ * RGBGR ++ */ ++ *dst++ = blue_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[curr[x] / 4]; ++ x++; ++ } ++} ++ ++void DebayerCpu::debayerIGIG10Line2(uint8_t *dst, const uint8_t *src[]) ++{ ++ const uint16_t *prev = (const uint16_t *)src[1]; ++ const uint16_t *curr = (const uint16_t *)src[2]; ++ const uint16_t *next = (const uint16_t *)src[3]; ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* ++ * IGIG line pixel 0: IGIGI ++ * GRGBG ++ * IGIGI ++ * GBGRG ++ * IGIGI ++ */ ++ *dst++ = blue_[(prev[x + 1] + next[x - 1]) / 8]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[(prev[x - 1] + next[x + 1]) / 8]; ++ x++; ++ ++ /* ++ * IGIG line pixel 1: GIGIG ++ * RGBGR ++ * GIGIG ++ * BGRGB ++ * GIGIG ++ */ ++ *dst++ = blue_[prev[x] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[next[x] / 4]; ++ x++; ++ ++ /* ++ * IGIG line pixel 2: IGIGI ++ * GBGRG ++ * IGIGI ++ * GRGBG ++ * IGIGI ++ */ ++ *dst++ = blue_[(prev[x - 1] + next[x + 1]) / 8]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[(prev[x + 1] + next[x - 1]) / 8]; ++ x++; ++ ++ /* ++ * IGIG line pixel 3: GIGIG ++ * BGRGB ++ * GIGIG ++ * RGBGR ++ * GIGIG ++ */ ++ *dst++ = blue_[next[x] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[prev[x] / 4]; ++ x++; ++ } ++} ++ ++void DebayerCpu::debayerGRGB10Line3(uint8_t *dst, const uint8_t *src[]) ++{ ++ const uint16_t *prev2 = (const uint16_t *)src[0]; ++ const uint16_t *prev = (const uint16_t *)src[1]; ++ const uint16_t *curr = (const uint16_t *)src[2]; ++ const uint16_t *next = (const uint16_t *)src[3]; ++ const uint16_t *next2 = (const uint16_t *)src[4]; ++ ++ for (int x = 0; x < (int)window_.width;) { ++ /* ++ * GRGB line pixel 0: GRGBG ++ * IGIGI ++ * GBGRG ++ * IGIGI ++ * GRGBG ++ */ ++ *dst++ = blue_[curr[x - 1] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[curr[x + 1] / 4]; ++ x++; ++ ++ /* ++ * GRGB line pixel 1: RGBGR ++ * GIGIG ++ * BGRGB ++ * GIGIG ++ * RGBGR ++ */ ++ *dst++ = blue_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[curr[x] / 4]; ++ x++; ++ ++ /* ++ * GRGB line pixel 2: GBGRG ++ * IGIGI ++ * GRGBG ++ * IGIGI ++ * GBGRG ++ */ ++ *dst++ = blue_[curr[x + 1] / 4]; ++ *dst++ = green_[curr[x] / 4]; ++ *dst++ = red_[curr[x - 1] / 4]; ++ x++; ++ ++ /* ++ * GRGB line pixel 3: BGRGB ++ * GIGIG ++ * RGBGR ++ * GIGIG ++ * BGRGB ++ */ ++ *dst++ = blue_[curr[x] / 4]; ++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; ++ *dst++ = red_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; ++ x++; ++ } ++} ++ + static bool isStandardBayerOrder(BayerFormat::Order order) + { + return order == BayerFormat::BGGR || order == BayerFormat::GBRG || +@@ -259,6 +491,15 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + return 0; + } + ++ if (bayerFormat.bitDepth == 10 && bayerFormat.packing == BayerFormat::Packing::None && ++ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) { ++ config.bpp = 16; ++ config.patternSize.height = 4; ++ config.patternSize.width = 4; ++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 }); ++ return 0; ++ } ++ + LOG(Debayer, Info) + << "Unsupported input format " << inputFormat.toString(); + return -EINVAL; +@@ -384,6 +625,16 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF + } + } + ++ if (bayerFormat.bitDepth == 10 && ++ bayerFormat.packing == BayerFormat::Packing::None && ++ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) { ++ debayer0_ = &DebayerCpu::debayerIGIG10Line0; ++ debayer1_ = &DebayerCpu::debayerGBGR10Line1; ++ debayer2_ = &DebayerCpu::debayerIGIG10Line2; ++ debayer3_ = &DebayerCpu::debayerGRGB10Line3; ++ return 0; ++ } ++ + invalid_fmt: + LOG(Debayer, Error) << "Unsupported input output format combination"; + return -EINVAL; +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch b/users/flokli/ipu6-softisp/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch new file mode 100644 index 000000000000..ec4991769913 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch @@ -0,0 +1,130 @@ +From 26e96232c314f9d34f6ee3be365c04918967084e Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Mon, 22 Jan 2024 17:18:00 +0100 +Subject: [PATCH 23/25] libcamera: Add "Software ISP benchmarking" + documentation + +Add a "Software ISP benchmarking" documentation section which describes +the performance/power consumption measurements used during +the Software ISP's development. + +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + Documentation/index.rst | 1 + + Documentation/meson.build | 1 + + Documentation/software-isp-benchmarking.rst | 82 +++++++++++++++++++++ + 3 files changed, 84 insertions(+) + create mode 100644 Documentation/software-isp-benchmarking.rst + +diff --git a/Documentation/index.rst b/Documentation/index.rst +index 63fac72d..5442ae75 100644 +--- a/Documentation/index.rst ++++ b/Documentation/index.rst +@@ -24,3 +24,4 @@ + Lens driver requirements <lens_driver_requirements> + Python Bindings <python-bindings> + Camera Sensor Model <camera-sensor-model> ++ SoftwareISP Benchmarking <software-isp-benchmarking> +diff --git a/Documentation/meson.build b/Documentation/meson.build +index 7a58fec8..3872e0a8 100644 +--- a/Documentation/meson.build ++++ b/Documentation/meson.build +@@ -80,6 +80,7 @@ if sphinx.found() + 'lens_driver_requirements.rst', + 'python-bindings.rst', + 'sensor_driver_requirements.rst', ++ 'software-isp-benchmarking.rst', + '../README.rst', + ] + +diff --git a/Documentation/software-isp-benchmarking.rst b/Documentation/software-isp-benchmarking.rst +new file mode 100644 +index 00000000..738c8c65 +--- /dev/null ++++ b/Documentation/software-isp-benchmarking.rst +@@ -0,0 +1,82 @@ ++.. SPDX-License-Identifier: CC-BY-SA-4.0 ++ ++.. _software-isp-benchmarking: ++ ++Software ISP benchmarking ++========================= ++ ++The Software ISP is paricular sensitive to performance regressions ++therefor it is a good idea to always benchmark the Software ISP ++before and after making changes to it and ensure that there are ++no performance regressions. ++ ++DebayerCpu class builtin benchmark ++---------------------------------- ++ ++The DebayerCpu class has a builtin benchmark. This benchmark ++measures the time spend on processing (collecting statistics ++and debayering) only, it does not measure the time spend on ++capturing or outputting the frames. ++ ++The builtin benchmark always runs. So this can be used by simply ++running "cam" or "qcam" with a pipeline using the Software ISP. ++ ++When it runs it will skip measuring the first 30 frames to ++allow the caches and the CPU temperature (turbo-ing) to warm-up ++and then it measures 30 fps and shows the total and per frame ++processing time using an info level log message: ++ ++.. code-block:: ++ ++ INFO Debayer debayer_cpu.cpp:907 Processed 30 frames in 244317us, 8143 us/frame ++ ++To get stable measurements it is advised to disable any other processes which ++may cause significant CPU usage (e.g. disable wifi, bluetooth and browsers). ++When possible it is also advisable to disable CPU turbo-ing and ++frequency-scaling. ++ ++For example when benchmarking on a Lenovo ThinkPad X1 Yoga Gen 8, with ++the charger plugged in, the CPU can be fixed to run at 2 GHz using: ++ ++.. code-block:: ++ ++ sudo x86_energy_perf_policy --turbo-enable 0 ++ sudo cpupower frequency-set -d 2GHz -u 2GHz ++ ++with these settings the builtin bench reports a processing time of ~7.8ms/frame ++on this laptop for FHD SGRBG10 (unpacked) bayer data. ++ ++Measuring power consumption ++--------------------------- ++ ++Since the Software ISP is often used on mobile devices it is also ++important to measure power consumption and ensure that that does ++not regress. ++ ++For example to measure power consumption on a Lenovo ThinkPad X1 Yoga Gen 8 ++it needs to be running on battery and it should be configured with its ++platform-profile (/sys/firmware/acpi/platform_profile) set to balanced and ++with its default turbo and frequency-scaling behavior to match real world usage. ++ ++Then start qcam to capture a FHD picture at 30 fps and position the qcam window ++so that it is fully visible. After this run the following command to monitor ++the power consumption: ++ ++.. code-block:: ++ ++ watch -n 10 cat /sys/class/power_supply/BAT0/power_now /sys/class/hwmon/hwmon6/fan?_input ++ ++Note this not only measures the power consumption in ลณW it also monitors ++the speed of this laptop's 2 fans. This is important because depending on ++the ambient temperature the 2 fans may spin up while testing and this ++will cause an additional power consumption of approx. 0.5W messing up ++the measurement. ++ ++After starting qcam + the watch command let the laptop sit without using ++it for 2 minutes for the readings to stabilize. Then check that the fans ++have not turned on and manually take a couple of consecutive power readings ++and avarage these. ++ ++On the example Lenovo ThinkPad X1 Yoga Gen 8 laptop this results in ++a measured power consumption of approx. 13W while running qcam versus ++approx. 4-5W while setting idle with its OLED panel on. +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0024-ov01a1s-HACK.patch b/users/flokli/ipu6-softisp/libcamera/0024-ov01a1s-HACK.patch new file mode 100644 index 000000000000..3b558e06d0e9 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0024-ov01a1s-HACK.patch @@ -0,0 +1,95 @@ +From 9bec33e5c7e6765734eeef2d22d7f7f65dee2264 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Tue, 19 Dec 2023 15:45:51 +0100 +Subject: [PATCH 24/25] ov01a1s HACK + +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + src/libcamera/camera_sensor.cpp | 6 ++++++ + src/libcamera/software_isp/debayer_cpu.cpp | 8 ++++++++ + src/libcamera/software_isp/swstats_cpu.cpp | 5 +++++ + 3 files changed, 19 insertions(+) + +diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/camera_sensor.cpp +index f19f72ea..7ad4b9ef 100644 +--- a/src/libcamera/camera_sensor.cpp ++++ b/src/libcamera/camera_sensor.cpp +@@ -34,6 +34,9 @@ + + namespace libcamera { + ++// HACK HACK ++bool is_ov01a1s = false; ++ + LOG_DEFINE_CATEGORY(CameraSensor) + + /** +@@ -426,6 +429,9 @@ int CameraSensor::initProperties() + model_ = subdev_->model(); + properties_.set(properties::Model, utils::toAscii(model_)); + ++ if (model_ == "ov01a1s") ++ is_ov01a1s = true; ++ + /* Generate a unique ID for the sensor. */ + int ret = generateId(); + if (ret) +diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp +index 41c8805f..b6393925 100644 +--- a/src/libcamera/software_isp/debayer_cpu.cpp ++++ b/src/libcamera/software_isp/debayer_cpu.cpp +@@ -23,6 +23,8 @@ + + namespace libcamera { + ++extern bool is_ov01a1s; ++ + DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats) + : stats_(std::move(stats)), gamma_correction_(1.0) + { +@@ -471,6 +473,9 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputFormat); + ++ if (is_ov01a1s) ++ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB; ++ + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && + bayerFormat.packing == BayerFormat::Packing::None && + isStandardBayerOrder(bayerFormat.order)) { +@@ -548,6 +553,9 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputFormat); + ++ if (is_ov01a1s) ++ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB; ++ + swapRedBlueGains_ = false; + + switch (outputFormat) { +diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp +index 96e21be5..503ce799 100644 +--- a/src/libcamera/software_isp/swstats_cpu.cpp ++++ b/src/libcamera/software_isp/swstats_cpu.cpp +@@ -19,6 +19,8 @@ + + namespace libcamera { + ++extern bool is_ov01a1s; ++ + SwStatsCpu::SwStatsCpu() + : SwStats() + { +@@ -301,6 +303,9 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg) + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + ++ if (is_ov01a1s) ++ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB; ++ + startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats; + finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats; + +-- +2.43.0 + diff --git a/users/flokli/ipu6-softisp/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch b/users/flokli/ipu6-softisp/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch new file mode 100644 index 000000000000..1f267353c5e3 --- /dev/null +++ b/users/flokli/ipu6-softisp/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch @@ -0,0 +1,40 @@ +From 4f2c94ba8b7f9f4d85a1d7e03f4c5272d92c3361 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Wed, 24 Jan 2024 20:44:29 +0100 +Subject: [PATCH 25/25] libcamera: debayer_cpu: Make the minimum size 1280x720 + +pipewire + firefox default to what looks like 640x480 if we export +the entire supported cropping range. Hardcode 720p as minsize for now. + +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + include/libcamera/internal/software_isp/debayer.h | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) + +diff --git a/include/libcamera/internal/software_isp/debayer.h b/include/libcamera/internal/software_isp/debayer.h +index 39e6f393..4348173d 100644 +--- a/include/libcamera/internal/software_isp/debayer.h ++++ b/include/libcamera/internal/software_isp/debayer.h +@@ -112,9 +112,16 @@ public: + return {}; + } + +- return SizeRange(Size(pattern_size.width, pattern_size.height), +- Size((inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1), +- (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1)), ++ /* ++ * pipewire + firefox default to what looks like 640x480 ++ * if we export the entire supported cropping range. ++ * Hardcode 720p as minsize for now. Minsize should be ++ * Size(pattern_size.width, pattern_size.height) ++ */ ++ unsigned int w = (inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1); ++ unsigned int h = (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1); ++ return SizeRange(Size(std::min(w, 1280u), std::min(h, 720u)), ++ Size(w, h), + pattern_size.width, pattern_size.height); + } + +-- +2.43.0 + diff --git a/users/flokli/keyboards/dilemma/default.nix b/users/flokli/keyboards/dilemma/default.nix new file mode 100644 index 000000000000..265f8e56dbf0 --- /dev/null +++ b/users/flokli/keyboards/dilemma/default.nix @@ -0,0 +1,45 @@ +{ depot, pkgs, ... }: + +rec { + firmware = pkgs.stdenv.mkDerivation { + name = "keychron-bastardkb-dilemma-firmware"; + + src = pkgs.fetchFromGitHub { + owner = "qmk"; + repo = "qmk_firmware"; + rev = "728aa576b0cd65c6fb7cf77132fdcd06fcedb643"; # develop branch + hash = "sha256-YmdX8nEsB1R8d265HAmvwejPjEHJdoTnm4QNigzrcyw="; + fetchSubmodules = true; + }; + + patches = [ ./enable-taps.patch ]; + + postPatch = '' + patchShebangs util/uf2conv.py + ''; + + nativeBuildInputs = [ + pkgs.python3 + pkgs.qmk + ]; + + buildPhase = '' + mkdir -p keyboards/bastardkb/dilemma/3x5_3/keymaps/flokli + cp ${./keymap.c} keyboards/bastardkb/dilemma/3x5_3/keymaps/flokli/keymap.c + cp ${./rules.mk} keyboards/bastardkb/dilemma/3x5_3/keymaps/flokli/rules.mk + + make bastardkb/dilemma/3x5_3:flokli + ''; + + installPhase = '' + mkdir -p $out + cp bastardkb_dilemma_3x5_3_flokli.uf2 $out/ + ''; + }; + + flash = pkgs.writeShellScript "flash.sh" '' + ${pkgs.qmk}/bin/qmk flash ${firmware}/bastardkb_dilemma_3x5_3_flokli.uf2 + ''; + + meta.ci.targets = [ "firmware" ]; +} diff --git a/users/flokli/keyboards/dilemma/enable-taps.patch b/users/flokli/keyboards/dilemma/enable-taps.patch new file mode 100644 index 000000000000..afded8549210 --- /dev/null +++ b/users/flokli/keyboards/dilemma/enable-taps.patch @@ -0,0 +1,24 @@ +From 32a1b9a189c13bec03c6b0f258121c47185db0ad Mon Sep 17 00:00:00 2001 +From: Florian Klink <flokli@flokli.de> +Date: Tue, 23 Jan 2024 11:26:10 +0200 +Subject: [PATCH] bastardkb dilemma: enable taps + +--- + keyboards/bastardkb/dilemma/3x5_3/config.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/keyboards/bastardkb/dilemma/3x5_3/config.h b/keyboards/bastardkb/dilemma/3x5_3/config.h +index ccbc4e2f58..bf17dc7e02 100644 +--- a/keyboards/bastardkb/dilemma/3x5_3/config.h ++++ b/keyboards/bastardkb/dilemma/3x5_3/config.h +@@ -42,6 +42,7 @@ + #define POINTING_DEVICE_CS_PIN GP21 + #undef CIRQUE_PINNACLE_DIAMETER_MM + #define CIRQUE_PINNACLE_DIAMETER_MM 40 ++#define CIRQUE_PINNACLE_TAP_ENABLE 1 + + /* Reset. */ + #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET +-- +2.43.0 + diff --git a/users/flokli/keyboards/dilemma/keymap.c b/users/flokli/keyboards/dilemma/keymap.c new file mode 100644 index 000000000000..726a6406bea0 --- /dev/null +++ b/users/flokli/keyboards/dilemma/keymap.c @@ -0,0 +1,220 @@ +/** + * Copyright 2022 Charly Delay <charly@codesink.dev> (@0xcharly) + * Copyright 2023 casuanoob <casuanoob@hotmail.com> (@casuanoob) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include QMK_KEYBOARD_H + +enum dilemma_keymap_layers { + LAYER_BASE = 0, + LAYER_NAVIGATION, + LAYER_MOUSE, + LAYER_MEDIA, + LAYER_NUMERAL, + LAYER_SYMBOLS, + LAYER_FUNCTION, +}; + +// Automatically enable sniping-mode on the pointer layer. +#define DILEMMA_AUTO_SNIPING_ON_LAYER LAYER_MOUSE +#define ESC_MED LT(LAYER_MEDIA, KC_ESC) +#define SPC_NAV LT(LAYER_NAVIGATION, KC_SPC) +#define TAB_MOU LT(LAYER_MOUSE, KC_TAB) +#define ENT_SYM LT(LAYER_SYMBOLS, KC_ENT) +#define BSP_NUM LT(LAYER_NUMERAL, KC_BSPC) +#define DEL_FUN LT(LAYER_FUNCTION, KC_DEL) + +#ifndef POINTING_DEVICE_ENABLE +# define DRGSCRL KC_NO +# define DPI_MOD KC_NO +# define S_D_MOD KC_NO +# define SNIPING KC_NO +#endif // !POINTING_DEVICE_ENABLE + +// clang-format off +/** \brief QWERTY layout (3 rows, 10 columns). */ +#define LAYOUT_LAYER_BASE \ + KC_Q, KC_W, KC_F, KC_P, KC_B, KC_J, KC_L, KC_U, KC_Y, KC_QUOT, \ + KC_A, KC_R, KC_S, KC_T, KC_G, KC_M, KC_N, KC_E, KC_I, KC_O, \ + KC_Z, KC_X, KC_C, KC_D, KC_V, KC_K, KC_H, KC_COMMA, KC_DOT, KC_SLSH, \ + ESC_MED, SPC_NAV, TAB_MOU, ENT_SYM, BSP_NUM, DEL_FUN + +/** Convenience row shorthands. */ +#define _______________DEAD_HALF_ROW_______________ XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX +#define ______________HOME_ROW_GACS_L______________ KC_LGUI, KC_LALT, KC_LCTL, KC_LSFT, XXXXXXX +#define ______________HOME_ROW_GACS_R______________ XXXXXXX, KC_LSFT, KC_LCTL, KC_LALT, KC_LGUI + +/* + * Layers used on the Dilemma. + * + * These layers started off heavily inspired by the Miryoku layout, but trimmed + * down and tailored for a stock experience that is meant to be fundation for + * further personalization. + * + * See https://github.com/manna-harbour/miryoku for the original layout. + */ + +/** + * \brief Navigation layer. + * + * Primary right-hand layer (left home thumb) is navigation and editing. Cursor + * keys are on the home position, line and page movement below, clipboard + * above, caps lock and insert on the inner column. Thumb keys are duplicated + * from the base layer to avoid having to layer change mid edit and to enable + * auto-repeat. +*/ +#define LAYOUT_LAYER_NAVIGATION \ + _______________DEAD_HALF_ROW_______________, KC_AGAIN,KC_PSTE, KC_COPY, KC_CUT, KC_UNDO, \ + ______________HOME_ROW_GACS_L______________, KC_CAPS, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, \ + _______________DEAD_HALF_ROW_______________, KC_INS, KC_HOME, KC_PGDN, KC_PGUP, KC_END, \ + XXXXXXX, _______, XXXXXXX, KC_ENT, KC_BSPC, KC_DEL + +/** + * \brief Mouse layer + * + * Secondary right-hand layer is mouse emulation. Mouse movement mirrors cursor + * navigation on home and wheel mirrors line / page movement below. Mouse + * buttons are on the thumbs. Left, right, and middle mouse buttons are on the + * primary, secondary, and tertiary thumb keys, respectively. Mouse movement, + * click, and drag, with modifiers, can be performed from the home position. + * Clipboard keys are duplicated from the Nav layer. +*/ +#define LAYOUT_LAYER_MOUSE \ + _______________DEAD_HALF_ROW_______________, KC_AGAIN,KC_PSTE, KC_COPY, KC_CUT, KC_UNDO, \ + ______________HOME_ROW_GACS_L______________, _______, KC_MS_L, KC_MS_D, KC_MS_U, KC_MS_R, \ + _______________DEAD_HALF_ROW_______________, _______, KC_WH_L, KC_WH_D, KC_WH_U, KC_WH_R, \ + XXXXXXX, XXXXXXX, _______, KC_BTN2, KC_BTN1, KC_BTN3 + +/** + * \brief Media layer + * + * Tertiary right-hand layer is media control, with volume up / volume down and + * next / prev mirroring the navigation keys. Pause, stop and mute are on the + * primary, secondary, and tertiary thumbs, respectively. + * + * Keyboard hardware controls are also present, and depend on hardware and + * firmware support. + * + * RGB control is on the top row. RGB Toggle is on the inner index column key. + * Combine with Shift for RGB Off. RGB Mode, RGB Hue, RGB Saturation, and RGB + * Value are on index, middle, ring, and pinkie column keys, respectively. + * Tapping will increase the corresponding value. Combine with Shift to + * decrease. +*/ +#define LAYOUT_LAYER_MEDIA \ + _______________DEAD_HALF_ROW_______________, RGB_TOG, RGB_MOD, RGB_HUI, RGB_SAI, RGB_VAI, \ + ______________HOME_ROW_GACS_L______________, _______, KC_MPRV, KC_VOLD, KC_VOLU, KC_MNXT, \ + _______________DEAD_HALF_ROW_______________, _______, _______, _______, _______, _______, \ + _______, XXXXXXX, XXXXXXX, KC_MSTP, KC_MPLY, KC_MUTE + +/** + * \brief Numeral layout. + * + * Primary left-hand layer (right home thumb) is numerals and symbols. Numerals + * are in the standard numpad locations with symbols in the remaining positions. + */ +#define LAYOUT_LAYER_NUMERAL \ + KC_LBRC, KC_7, KC_8, KC_9, KC_RBRC, _______________DEAD_HALF_ROW_______________, \ + KC_SCLN, KC_4, KC_5, KC_6, KC_EQL, ______________HOME_ROW_GACS_R______________, \ + KC_DOT, KC_1, KC_2, KC_3, KC_BSLS, _______________DEAD_HALF_ROW_______________, \ + KC_DOT, KC_0, KC_MINS, XXXXXXX, _______, XXXXXXX + +/** + * \brief Symbols layer. + * + * Secondary left-hand layer has shifted symbols in the same locations to reduce + * chording when using mods with shifted symbols. `KC_LPRN` is duplicated next to + * `KC_RPRN`. + */ +#define LAYOUT_LAYER_SYMBOLS \ + KC_LCBR, KC_AMPR, KC_ASTR, KC_LPRN, KC_RCBR, _______________DEAD_HALF_ROW_______________, \ + KC_COLN, KC_DLR, KC_PERC, KC_CIRC, KC_PLUS, ______________HOME_ROW_GACS_R______________, \ + KC_TILD, KC_EXLM, KC_AT, KC_HASH, KC_PIPE, _______________DEAD_HALF_ROW_______________, \ + KC_LPRN, KC_RPRN, KC_UNDS, _______, XXXXXXX, XXXXXXX + +/** + * \brief Function layer. + * + * Secondary right-hand layer has function keys mirroring the numerals on the + * primary layer with extras on the pinkie column, plus system keys on the inner + * column. App is on the tertiary thumb key and other thumb keys are duplicated + * from the base layer to enable auto-repeat. + */ +#define LAYOUT_LAYER_FUNCTION \ + KC_F12, KC_F7, KC_F8, KC_F9, KC_PSCR, _______________DEAD_HALF_ROW_______________, \ + KC_F11, KC_F4, KC_F5, KC_F6, KC_SCRL, ______________HOME_ROW_GACS_R______________, \ + KC_F10, KC_F1, KC_F2, KC_F3, KC_PAUS, _______________DEAD_HALF_ROW_______________, \ + KC_APP, KC_SPC, KC_TAB, XXXXXXX, XXXXXXX, _______ + +/** + * \brief Add Home Row mod to a layout. + * + * Expects a 10-key per row layout. Adds support for GACS (Gui, Alt, Ctl, Shift) + * home row. The layout passed in parameter must contain at least 20 keycodes. + * + * This is meant to be used with `LAYER_BASE` defined above, eg.: + * + * HOME_ROW_MOD_GACS(LAYER_BASE) + */ +#define _HOME_ROW_MOD_GACS( \ + L00, L01, L02, L03, L04, R05, R06, R07, R08, R09, \ + L10, L11, L12, L13, L14, R15, R16, R17, R18, R19, \ + ...) \ + L00, L01, L02, L03, L04, \ + R05, R06, R07, R08, R09, \ + LGUI_T(L10), LALT_T(L11), LCTL_T(L12), LSFT_T(L13), L14, \ + R15, RSFT_T(R16), RCTL_T(R17), LALT_T(R18), RGUI_T(R19), \ + __VA_ARGS__ +#define HOME_ROW_MOD_GACS(...) _HOME_ROW_MOD_GACS(__VA_ARGS__) + + +#define LAYOUT_wrapper(...) LAYOUT_split_3x5_3(__VA_ARGS__) + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + [LAYER_BASE] = LAYOUT_wrapper( + HOME_ROW_MOD_GACS(LAYOUT_LAYER_BASE) + ), + [LAYER_NAVIGATION] = LAYOUT_wrapper(LAYOUT_LAYER_NAVIGATION), + [LAYER_MOUSE] = LAYOUT_wrapper(LAYOUT_LAYER_MOUSE), + [LAYER_MEDIA] = LAYOUT_wrapper(LAYOUT_LAYER_MEDIA), + [LAYER_NUMERAL] = LAYOUT_wrapper(LAYOUT_LAYER_NUMERAL), + [LAYER_SYMBOLS] = LAYOUT_wrapper(LAYOUT_LAYER_SYMBOLS), + [LAYER_FUNCTION] = LAYOUT_wrapper(LAYOUT_LAYER_FUNCTION), +}; +// clang-format on + +#ifdef POINTING_DEVICE_ENABLE +# ifdef DILEMMA_AUTO_SNIPING_ON_LAYER +layer_state_t layer_state_set_user(layer_state_t state) { + dilemma_set_pointer_sniping_enabled(layer_state_cmp(state, DILEMMA_AUTO_SNIPING_ON_LAYER)); + return state; +} +# endif // DILEMMA_AUTO_SNIPING_ON_LAYER +#endif // POINTING_DEVICE_ENABLE + +#ifdef ENCODER_MAP_ENABLE +// clang-format off +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = { + [LAYER_BASE] = {ENCODER_CCW_CW(KC_WH_D, KC_WH_U), ENCODER_CCW_CW(KC_VOLD, KC_VOLU)}, + [LAYER_NAVIGATION] = {ENCODER_CCW_CW(KC_PGDN, KC_PGUP), ENCODER_CCW_CW(KC_VOLU, KC_VOLD)}, + [LAYER_MOUSE] = {ENCODER_CCW_CW(RGB_HUD, RGB_HUI), ENCODER_CCW_CW(RGB_SAD, RGB_SAI)}, + [LAYER_MEDIA] = {ENCODER_CCW_CW(KC_PGDN, KC_PGUP), ENCODER_CCW_CW(KC_VOLU, KC_VOLD)}, + [LAYER_NUMERAL] = {ENCODER_CCW_CW(RGB_VAD, RGB_VAI), ENCODER_CCW_CW(RGB_SPD, RGB_SPI)}, + [LAYER_SYMBOLS] = {ENCODER_CCW_CW(RGB_RMOD, RGB_MOD), ENCODER_CCW_CW(KC_LEFT, KC_RGHT)}, + [LAYER_FUNCTION] = {ENCODER_CCW_CW(KC_DOWN, KC_UP), ENCODER_CCW_CW(KC_LEFT, KC_RGHT)}, +}; +// clang-format on +#endif // ENCODER_MAP_ENABLE diff --git a/users/flokli/keyboards/dilemma/rules.mk b/users/flokli/keyboards/dilemma/rules.mk new file mode 100644 index 000000000000..5a090013dc7b --- /dev/null +++ b/users/flokli/keyboards/dilemma/rules.mk @@ -0,0 +1,2 @@ +ENCODER_MAP_ENABLE = yes +OPT_DEFS += -DMK_KINETIC_SPEED=1 diff --git a/users/flokli/keyboards/k6_pro/default.nix b/users/flokli/keyboards/k6_pro/default.nix new file mode 100644 index 000000000000..708bec7313b6 --- /dev/null +++ b/users/flokli/keyboards/k6_pro/default.nix @@ -0,0 +1,39 @@ +{ depot, pkgs, ... }: + +rec { + firmware = pkgs.stdenv.mkDerivation { + name = "keychron-k6_pro-firmware"; + + src = pkgs.fetchFromGitHub { + owner = "Keychron"; # the Keychron fork of qmk/qmk_firmware + repo = "qmk_firmware"; + rev = "e0a48783e7cde92d1edfc53a8fff511c45e869d4"; # bluetooth_playground branch + hash = "sha256-Pk9kXktmej9JyvSt7UMEW2FDrBg7k1lOssh6HjrP5ro="; + fetchSubmodules = true; + }; + + nativeBuildInputs = [ + pkgs.qmk + ]; + + buildPhase = '' + mkdir -p keyboards/keychron/k6_pro/ansi/rgb/keymaps/flokli + cp ${./keymap.c} keyboards/keychron/k6_pro/ansi/rgb/keymaps/flokli/keymap.c + cp ${./rules.mk} keyboards/keychron/k6_pro/ansi/rgb/keymaps/flokli/rules.mk + + make keychron/k6_pro/ansi/rgb:flokli + ''; + + installPhase = '' + mkdir -p $out + + cp keychron_k6_pro_ansi_rgb_flokli.bin $out/ + ''; + }; + + flash = pkgs.writeShellScript "flash.sh" '' + ${pkgs.qmk}/bin/qmk flash ${firmware}/keychron_k6_pro_ansi_rgb_flokli.bin + ''; + + meta.ci.targets = [ "firmware" ]; +} diff --git a/users/flokli/keyboards/k6_pro/keymap.c b/users/flokli/keyboards/k6_pro/keymap.c new file mode 100644 index 000000000000..1aa406eeac3f --- /dev/null +++ b/users/flokli/keyboards/k6_pro/keymap.c @@ -0,0 +1,76 @@ +/* Copyright 2021 @ Keychron (https://www.keychron.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include QMK_KEYBOARD_H + +// clang-format off +enum layers{ + MAC_BASE, + WIN_BASE, + MAC_FN1, + WIN_FN1, + FN2 +}; + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { +[MAC_BASE] = LAYOUT_ansi_68( + KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_DEL, + KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_HOME, + KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_PGUP, + KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_PGDN, + KC_LCTL, KC_LOPTN, KC_LCMMD, KC_SPC, KC_RCMMD,MO(MAC_FN1),MO(FN2), KC_LEFT, KC_DOWN, KC_RGHT), + +[WIN_BASE] = LAYOUT_ansi_68( + KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_DEL, + KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_HOME, + KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_PGUP, + KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_PGDN, + KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, MO(WIN_FN1),MO(FN2), KC_LEFT, KC_DOWN, KC_RGHT), + +[MAC_FN1] = LAYOUT_ansi_68( + KC_GRV, KC_BRID, KC_BRIU, KC_MCTL, KC_LPAD, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, _______, RGB_TOG, + _______, BT_HST1, BT_HST2, BT_HST3, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + RGB_TOG, RGB_MOD, RGB_VAI, RGB_HUI, RGB_SAI, RGB_SPI, _______, _______, _______, _______, _______, _______, _______, _______, + _______, RGB_RMOD, RGB_VAD, RGB_HUD, RGB_SAD, RGB_SPD, NK_TOGG, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______), + +[WIN_FN1] = LAYOUT_ansi_68( +// mic mute webcam wifi + KC_GRV, KC_MUTE, KC_VOLD, KC_VOLU, _______, KC_BRID, KC_BRIU, _______, _______, _______, _______, _______, _______, _______, RGB_TOG, + _______, BT_HST1, BT_HST2, BT_HST3, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + RGB_TOG, RGB_MOD, RGB_VAI, RGB_HUI, RGB_SAI, RGB_SPI, _______, _______, _______, _______, _______, _______, _______, _______, + _______, RGB_RMOD, RGB_VAD, RGB_HUD, RGB_SAD, RGB_SPD, NK_TOGG, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, KC_PSCR, _______, _______, _______, _______, _______), + +[FN2] = LAYOUT_ansi_68( + KC_TILD, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, KC_SLEP, + _______, KC_BTN1, KC_MS_U, KC_BTN2, KC_WH_U, KC_VOLU, KC_MUTE, KC_MPLY, _______, _______, _______, _______, _______, _______, _______, + _______, KC_MS_L, KC_MS_D, KC_MS_R, KC_WH_D, KC_VOLD, KC_MPRV, KC_MNXT, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, BAT_LVL, _______, _______, _______, _______, _______, _______, _______, _______, + _______, _______, _______, _______, _______, _______, _______, _______, _______, _______), +}; + +// Shift+Del -> middle mouse button +const key_override_t insert_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_DEL, KC_BTN3); +// Shift+Home -> End +const key_override_t end_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_HOME, KC_END); + +// This globally defines all key overrides to be used +const key_override_t **key_overrides = (const key_override_t *[]) { + &insert_key_override, + &end_key_override, + NULL // end of array +}; diff --git a/users/flokli/keyboards/k6_pro/rules.mk b/users/flokli/keyboards/k6_pro/rules.mk new file mode 100644 index 000000000000..35725756d41b --- /dev/null +++ b/users/flokli/keyboards/k6_pro/rules.mk @@ -0,0 +1,2 @@ +KEY_OVERRIDE_ENABLE = yes +OPT_DEFS += -DDYNAMIC_KEYMAP_LAYER_COUNT=5 -DMK_KINETIC_SPEED=1 diff --git a/users/flokli/keys.nix b/users/flokli/keys.nix new file mode 100644 index 000000000000..790c9862f824 --- /dev/null +++ b/users/flokli/keys.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + all = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTVTXOutUZZjXLB0lUSgeKcSY/8mxKkC0ingGK1whD2 flokli" + ]; +} diff --git a/users/flokli/nixos/.envrc b/users/flokli/nixos/.envrc new file mode 100644 index 000000000000..ccf3cb847ac5 --- /dev/null +++ b/users/flokli/nixos/.envrc @@ -0,0 +1 @@ +PATH_add $(nix-build ../../.. -A users.flokli.nixos.deps --no-out-link)/bin diff --git a/users/flokli/nixos/.skip-subtree b/users/flokli/nixos/.skip-subtree new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/flokli/nixos/.skip-subtree diff --git a/users/flokli/nixos/archeology-ec2/OWNERS b/users/flokli/nixos/archeology-ec2/OWNERS new file mode 100644 index 000000000000..b9bc074a8020 --- /dev/null +++ b/users/flokli/nixos/archeology-ec2/OWNERS @@ -0,0 +1 @@ +edef diff --git a/users/flokli/nixos/archeology-ec2/configuration.nix b/users/flokli/nixos/archeology-ec2/configuration.nix new file mode 100644 index 000000000000..f0fc0c5d095c --- /dev/null +++ b/users/flokli/nixos/archeology-ec2/configuration.nix @@ -0,0 +1,35 @@ +{ depot, pkgs, modulesPath, ... }: + +{ + imports = [ + "${modulesPath}/virtualisation/amazon-image.nix" + ../profiles/archeology.nix + ]; + + systemd.timers.parse-bucket-logs = { + wantedBy = [ "multi-user.target" ]; + timerConfig.OnCalendar = "*-*-* 03:00:00 UTC"; + }; + + systemd.services.parse-bucket-logs = { + path = [ depot.users.flokli.archeology.parse-bucket-logs ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = (pkgs.writers.writePython3 "parse-bucket-logs-continuously" + { + libraries = [ pkgs.python3Packages.boto3 ]; + } ./parse-bucket-logs-continuously.py); + DynamicUser = "yes"; + StateDirectory = "parse-bucket-logs"; + }; + }; + + environment.systemPackages = [ + depot.users.flokli.archeology.parse-bucket-logs + ]; + + networking.hostName = "archeology-ec2"; + + system.stateVersion = "23.05"; # Did you read the comment? +} + diff --git a/users/flokli/nixos/archeology-ec2/hardware-configuration.nix b/users/flokli/nixos/archeology-ec2/hardware-configuration.nix new file mode 100644 index 000000000000..7b3d79d70a5d --- /dev/null +++ b/users/flokli/nixos/archeology-ec2/hardware-configuration.nix @@ -0,0 +1,36 @@ +{ lib, modulesPath, ... }: + +{ + imports = + [ + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { + device = "/dev/disk/by-partlabel/root"; + fsType = "xfs"; + }; + + fileSystems."/boot" = + { + device = "/dev/disk/by-partlabel/boot"; + fsType = "vfat"; + }; + + swapDevices = [ ]; + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/users/flokli/nixos/archeology-ec2/parse-bucket-logs-continuously.py b/users/flokli/nixos/archeology-ec2/parse-bucket-logs-continuously.py new file mode 100644 index 000000000000..f6ec8fb77cef --- /dev/null +++ b/users/flokli/nixos/archeology-ec2/parse-bucket-logs-continuously.py @@ -0,0 +1,62 @@ +import boto3 +import datetime +import os +import re +import subprocess +import tempfile + +s3 = boto3.resource('s3') +bucket_name = "nix-archeologist" +prefix = "nix-cache-bucket-logs/" + +bucket = s3.Bucket(bucket_name) + +key_pattern = re.compile(r'.*\/(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})\.parquet$') # noqa: E501 + +# get a listing (which is sorted), grab the most recent key +last_elem = list( + o for o in bucket.objects.filter(Prefix=prefix) + if key_pattern.match(o.key) +).pop() + +# extract the date of that key. +m = key_pattern.search(last_elem.key) +last_elem_date = datetime.date(int(m.group("y")), int(m.group("m")), int(m.group("d"))) # noqa: E501 + +# get the current date (UTC) +now = datetime.datetime.now(tz=datetime.UTC) +now_date = datetime.date(now.year, now.month, now.day) + +while True: + # Calculate what date would be processed next. + next_elem_date = last_elem_date + datetime.timedelta(days=1) + + # If that's today, we don't want to process it. + if next_elem_date == now_date: + print("Caught up, would process data from today.") + break + + # If we'd be processing data from yesterday, but it's right after midnight, + # also don't process - data might still be flushed. + if (next_elem_date + datetime.timedelta(days=1) == now_date) and now.hour == 0: # noqa: E501 + print("Not processing data from previous day right after midnight") + break + + src = f"http://nix-cache-log.s3.amazonaws.com/log/{next_elem_date.isoformat()}-*" # noqa: E501 + + # Invoke parse-bucket-logs script inside a tempdir and upload on success. + with tempfile.TemporaryDirectory() as td: + work_file_name = os.path.join(td, "output.parquet") + args = ["archeology-parse-bucket-logs", src, work_file_name] + subprocess.run( + args, + check=True # throw exception if nonzero exit code + ) + + dest_key = f"{prefix}{next_elem_date.isoformat()}.parquet" + + # Upload the file + print(f"uploading to s3://{bucket_name}{dest_key}") + bucket.upload_file(work_file_name, dest_key) + + last_elem_date = next_elem_date diff --git a/users/flokli/nixos/default.nix b/users/flokli/nixos/default.nix new file mode 100644 index 000000000000..9ed223a90896 --- /dev/null +++ b/users/flokli/nixos/default.nix @@ -0,0 +1,32 @@ +{ depot, pkgs, lib, ... }: + +let + systemFor = sys: (depot.ops.nixos.nixosFor sys).system; + + # assumes `name` is configured appropriately in your .ssh/config + deployScript = name: sys: pkgs.writeShellScriptBin "deploy-${name}" '' + set -eo pipefail + nix-copy-closure --to ${name} --gzip --use-substitutes ${sys} + ssh ${name} nix-env --profile /nix/var/nix/profiles/system --set ${sys} + ssh ${name} ${sys}/bin/switch-to-configuration switch + ''; + +in +depot.nix.readTree.drvTargets rec { + archeologyEc2System = (depot.ops.nixos.nixosFor ({ ... }: { + imports = [ + ./archeology-ec2/configuration.nix + ]; + })).config.system.build.toplevel; + + deploy-archeology-ec2 = (deployScript "archeology-ec2" archeologyEc2System); + + deps = (depot.nix.lazy-deps { + deploy-archeology-ec2.attr = "users.flokli.nixos.deploy-archeology-ec2"; + }); + + shell = pkgs.mkShell { + name = "flokli-nixos-shell"; + packages = [ deps ]; + }; +} diff --git a/users/flokli/nixos/profiles/archeology.nix b/users/flokli/nixos/profiles/archeology.nix new file mode 100644 index 000000000000..c87d6bcf30fa --- /dev/null +++ b/users/flokli/nixos/profiles/archeology.nix @@ -0,0 +1,37 @@ +# Set of unconditional config options applicable to all archeology machines. + +{ depot, pkgs, ... }: + +{ + # Use the TVL binary cache + tvl.cache.enable = true; + + # Start clickhose as a system service. + services.clickhouse.enable = true; + + # for ClickHouse + # We're keeping this here rather than in the NixOS module, because I suspect + # this opens up timing side channels. This is a single-user, single-purpose + # machine, so that isn't a concern here. + boot.kernel.sysctl."kernel.task_delayacct" = 1; + + # Enable SSH and let edef and flokli in + services.openssh.enable = true; + + users.users.root.openssh.authorizedKeys.keys = [ + "cert-authority ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCvb/7ojfcbKvHIyjnrNUOOgzy44tCkgXY9HLuyFta1jQOE9pFIK19B4dR9bOglPKf145CCL0mSFJNNqmNwwavU2uRn+TQrW+U1dQAk8Gt+gh3O49YE854hwwyMU+xD6bIuUdfxPr+r5al/Ov5Km28ZMlHOs3FoAP0hInK+eAibioxL5rVJOtgicrOVCkGoXEgnuG+LRbOYTwzdClhRUxiPjK8alCbcJQ53AeZHO4G6w9wTr+W5ILCfvW4OmUXCX01sKzaBiQuuFCF6M/H4LlnsPWLMra2twXxkOIhZblwC+lncps9lQaUgiD4koZeOCORvHW00G0L39ilFbbnVcL6Itp/m8RRWm/xRxS4RMnsdV/AhvpRLrhL3lfQ7E2oCeSM36v1S9rdg6a47zcnpL+ahG76Gz39Y7KmVRQciNx7ezbwxj3Q5lZtFykgdfGIAN+bT8ijXMO6m68g60i9Bz4IoMZGkiJGqMYLTxMQ+oRgR3Ro5lbj7E11YBHyeimoBYXYGHMkiuxopQZ7lIj3plxIzhmUlXJBA4jMw9KGHdYaLhaicIYhvQmCTAjrkt2HvxEe6lU8iws2Qv+pB6tAGundN36RVVWAckeQPZ4ZsgDP8V2FfibZ1nsrQ+zBKqaslYMAHs01Cf0Hm0PnCqagf230xaobu0iooNuXx44QKoDnB+w== edef" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTVTXOutUZZjXLB0lUSgeKcSY/8mxKkC0ingGK1whD2 flokli" + ]; + + # Get a bunch of text editors and CLI tools. + environment.systemPackages = [ + pkgs.awscli + pkgs.duckdb + pkgs.parquet-tools + pkgs.helix + pkgs.htop + pkgs.kakoune + pkgs.kitty.terminfo + pkgs.tmux + ]; +} diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/.gitignore b/users/flokli/presentations/2023-09-09-nixcon-tvix/.gitignore new file mode 100644 index 000000000000..397b4a7624e3 --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/.gitignore @@ -0,0 +1 @@ +*.log diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/architecture.dot b/users/flokli/presentations/2023-09-09-nixcon-tvix/architecture.dot new file mode 100644 index 000000000000..a6ea0460efe6 --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/architecture.dot @@ -0,0 +1,5 @@ +digraph { + Builder + Store + Evaluator +} diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/cppnix-example-lexer.cpp b/users/flokli/presentations/2023-09-09-nixcon-tvix/cppnix-example-lexer.cpp new file mode 100644 index 000000000000..7c52bce8b6d2 --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/cppnix-example-lexer.cpp @@ -0,0 +1,13 @@ +attrpath + : attrpath '.' attr { + $$ = $1; $1->push_back(AttrName(data->symbols.create($3))); + } + | attrpath '.' string_attr + { $$ = $1; + ExprString * str = dynamic_cast<ExprString *>($3); + if (str) { + $$->push_back(AttrName(data->symbols.create(str->s))); + delete str; + } else + $$->push_back(AttrName($3)); + } diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/crate-deps.dot b/users/flokli/presentations/2023-09-09-nixcon-tvix/crate-deps.dot new file mode 100644 index 000000000000..66ead74b1e53 --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/crate-deps.dot @@ -0,0 +1,19 @@ +digraph { + bgcolor="transparent" + node [fillcolor="lightgrey",style="filled"] + + tvix_cli + tvix_eval + nix_compat + tvix_serde + tvix_store + + tvix_cli -> tvix_store + tvix_cli -> nix_compat + tvix_cli -> tvix_eval + + tvix_store -> nix_compat + tvix_eval -> nix_compat + + tvix_serde -> tvix_eval +} diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/default.nix b/users/flokli/presentations/2023-09-09-nixcon-tvix/default.nix new file mode 100644 index 000000000000..1ec0a0bd0ed7 --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/default.nix @@ -0,0 +1,37 @@ +{ depot, pkgs, ... }: + +let + inherit (pkgs) + fontconfig qrencode runCommand stdenv; + mkQr = url: runCommand "qrcode.png" { } '' + ${qrencode}/bin/qrencode -o $out -t SVG -s 5 \ + --background=fafafa \ + --foreground=000000 \ + ${url} + ''; +in +stdenv.mkDerivation { + name = "2023-nixcon-tvix"; + src = ./.; + + FONTCONFIG_FILE = pkgs.makeFontsConf { + fontDirectories = with pkgs; [ jetbrains-mono fira fira-code fira-mono lato ]; + }; + + PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium"; + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = "1"; + + nativeBuildInputs = [ pkgs.reveal-md pkgs.graphviz ]; + + buildPhase = '' + cp ${depot.tvix.logo}/logo.png tvix-logo.png + dot -Tsvg crate-deps.dot > crate-deps.svg + cp ${mkQr "https://flokli.de"} qrcode-flokli.svg + cp ${mkQr "https://tvix.dev"} qrcode-tvix.svg + + mkdir -p $out + reveal-md --static $out presentation.md + reveal-md --print $out/slides.pdf presentation.md + cp tvixbolt.webm $out + ''; +} diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/presentation.md b/users/flokli/presentations/2023-09-09-nixcon-tvix/presentation.md new file mode 100644 index 000000000000..0006763d6d82 --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/presentation.md @@ -0,0 +1,294 @@ +--- +author: +- Florian Klink +date: 2023-09-09 +title: "Tvix: Status update" +theme: moon +revealOptions: + transition: 'fade' +--- + +# Tvix: Status update + +![Tvix Logo](tvix-logo.png) + +2023-09-09 + +Florian Klink + +--- + +## Whoami + +- flokli +- nixpkgs contributor since 2018, maintaining systemd, nsncd and some + more stuff +- Freelance Nix/DevOps consultant +- I spend too much time on computers :-) + +--- + +## What is Tvix? + +- A new implementation of Nix +- modular +- written in Rust +- developed in the [TVL](https://tvl.fyi) monorepo +- subtree exported to [github:tvlfyi/tvix](https://github.com/tvlfyi/tvix) + +--- + +## Structure + +- strong separation between **Evaluator**, **Store** and **Builder** +- Defined interfaces between different components (Protobuf/gRPC) <!-- .element: class="fragment" --> + - Allows adding to/combining with your own components <!-- .element: class="fragment" --> +- <!-- .element: class="fragment" --> A lot of helper code for some of the Nix internals in the `nix-compat` crate + +Note: Derivation types, serializers. NAR writers, nixbase32 enc/dec, Nix Hash function, stringparsing etc. + +---- + +![crate-deps.svg](crate-deps.svg) + +--- + +## Evaluator: Design + +- <!-- .element: class="fragment" --> + Nix code is parsed via [rnix](https://github.com/nix-community/rnix-parser) +- <!-- .element: class="fragment" --> + AST traversal, generate bytecode (with some transformations) +- <!-- .element: class="fragment" --> + Bytecode is executed by an abstract machine with a Nix-specific instruction set + +---- + +## Evaluator: Design + +- <!-- .element: class="fragment" --> + Builtins are separate from the "evaluator core" + - <!-- .element: class="fragment" --> + inject your own builtins + - <!-- .element: class="fragment" --> + this includes `builtins.derivation`! +- <!-- .element: class="fragment" --> + IO is nicely abstracted away + - <!-- .element: class="fragment" --> + We can run a Nixlang subset without IO in wasm (see [tvixbolt](https://tvixbolt.tvl.su/)), + or parse Nix into a config struct with `tvix-serde` + +---- + +<!-- <video class="r-stretch" src="./tvixbolt.webm"></video> --> +<a href="./tvixbolt.webm">Tvixbolt Demo</a> + +---- + +### Evaluator: Current Work + +- <!-- .element: class="fragment" --> + Current goal: **evaluate nixpkgs the same way as Nix does** +- <!-- .element: class="fragment" --> + Checked by comparing the calculated output paths, which checks correctness of all \"parent\" output paths too. +- <!-- .element: class="fragment" --> + Required implementing a lot of Nix internals in `nix-compat`, and `tvix-store` (A-Term, hash modulo, NAR writer/hasher) + +Note: Getting output hashing correct, and exposing this in a re-usable fashion took quite some iterations to get right. + +---- + +### Evaluator: Current Work (cont.) +- <!-- .element: class="fragment" --> + ๐ Already correct for (and continously checked by CI on every commit): + - <!-- .element: class="fragment" --> + `stdenv`, `hello` + - <!-- .element: class="fragment" --> + `pkgsCross.aarch64-multiplatform.stdenv`, `pkgsCross.aarch64-multiplatform.hello` +- <!-- .element: class="fragment" --> + Some work left for more complicated expressions + - <!-- .element: class="fragment" --> + infinite recursion [when inheriting from a `builtins.tryEval` multiple times](https://b.tvl.fyi/281) + - <!-- .element: class="fragment" --> + small details around file imports +- <!-- .element: class="fragment" --> + Not too much performance finetuning until we're correct first. + +---- + +### Evaluator: Demo + +[![asciicast](https://asciinema.org/a/MH4tuVPLsKewJSGJUYEyIKUpj.svg)](https://asciinema.org/a/MH4tuVPLsKewJSGJUYEyIKUpj) + +--- + +## Store: Design + +- <!-- .element: class="fragment" --> + Uses a very different underlying data model: + - <!-- .element: class="fragment" --> + Nix stores on a per- `StorePath` granularity + - <!-- .element: class="fragment" --> + tvix-store uses a Merkle DAG of directories, similar to git trees, but with [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) digests as pointers. + - <!-- .element: class="fragment" --> + Compat layer in front to still render/calculate NAR on demand where needed + - <!-- .element: class="fragment" --> + Substitution, caching, ... possible to describe via composition/layering + +---- + +![tvix-store graph](tvix-store-graph.svg) + +---- + +### Store: Advantages + +- <!-- .element: class="fragment" --> + Less downloading of data that didn't change +- <!-- .element: class="fragment" --> + Less duplicate data on disk/storage +- <!-- .element: class="fragment" --> + Inherently content-addressed, so P2P substitution possible +- <!-- .element: class="fragment" --> + Allows doing verified blob streaming ([BAO](https://github.com/oconnor663/bao), [bao-tree](https://github.com/n0-computer/bao-tree)) +- <!-- .element: class="fragment" --> + Protocol has some \"smarter\" methods to avoid roundtrips, but could all be statically pre-rendered +- <!-- .element: class="fragment" --> + Very little data that needs to be fetched from a trusted party (or be signed) + +Note: Our way of addressing blobs by their raw blake3 digest is natively compatible with iroh, the IPFS Re-implementation in Rust + +---- + +### Store: Status + +- <!-- .element: class="fragment" --> + Whole Merkle-based store implementation (and Nix NAR compat layer) is there + - <!-- .element: class="fragment" --> + exercised by the output path CI tests, and a test suite. + - <!-- .element: class="fragment" --> + three backends (Sled, in-memory, gRPC client) + - <!-- .element: class="fragment" --> + more backends and more test suites planned. +- <!-- .element: class="fragment" --> + FUSE filesystem to expose the store (to Tvix Builders, \"appliances\") <!-- .element: class="fragment" --> + +Note: backends: RocksDB, sqlite, s3. fuse: lazy fetching of build input files | think about a minimal initrd to bring up network and mount the store, then run any closure. + +---- + +### Store: Demo + +[![asciicast](https://asciinema.org/a/YFB9yycHdx0OUH9N0WdAkIYua.svg)](https://asciinema.org/a/YFB9yycHdx0OUH9N0WdAkIYua) + +---- + +### Store: Status (cont.) +- <!-- .element: class="fragment" --> + More work on store composition needed (necessary for substition and caching) +- <!-- .element: class="fragment" --> + More work on more granular blob substititon needed. +- <!-- .element: class="fragment" --> + More work on bridges with Nix needed + - <!-- .element: class="fragment" --> + Get Nix to talk to a tvix-store + - <!-- .element: class="fragment" --> + Expose existing binary caches to tvix-store + +--- + +### Builder: Design + +- <!-- .element: class="fragment" --> + Build requests/Build protocol is less Nix-specific + - <!-- .element: class="fragment" --> + allows reusing builders for other usages (non-sandboxed builds, other build systems, playing with other addressing mechanisms) +- <!-- .element: class="fragment" --> + Distinction between a **specific build attempt** and the **general build recipe** + - <!-- .element: class="fragment" --> + allows keeping metadata about failed builds + - <!-- .element: class="fragment" --> + stats (memory/cpu consumption) + - <!-- .element: class="fragment" --> + comparing different produced binary outputs (r11y) + +---- + +### Builder: Design + +- <!-- .element: class="fragment" --> + Invididual builds can be run in your desired container/virt engine/scheduler, as long as it speaks the same Build API +- <!-- .element: class="fragment" --> + Build API composition/proxying, similar to Store composition +- <!-- .element: class="fragment" --> + allows "unattended building" (evaluate nixpkgs locally and send all build requests to a remote builder) +- <!-- .element: class="fragment" --> + allows tailing logs from currently/already running builds + +---- + +### Builder: Status + +- <!-- .element: class="fragment" --> + Dummy Builder implementation in `go-nix` (using OCI) +- <!-- .element: class="fragment" --> + Some scribble notes on the Build Protocol +- <!-- .element: class="fragment" --> + Glue code to trigger builds from inside `builtins.derivation` needs to be written +- <!-- .element: class="fragment" --> + Builder implementation (using `systemd-nspwan` or some container engine needs to be written. +- <!-- .element: class="fragment" --> + Web interface to visualize store contents and build graphs/builds/logs + +--- + +## Contributing + +- <!-- .element: class="fragment" --> + Join the IRC channel (`#tvl` on `hackint`), bridged to Matrix and XMPP +- <!-- .element: class="fragment" --> + Check our issue tracker +- <!-- .element: class="fragment" --> + Try to use it and tell us how you broke it! +- <!-- .element: class="fragment" --> + Add various Nix bits to `nix-compat` + +Note: or if you like what you seeing and want to sponsor parts, that's also cool :-) + +--- + +# Thanks to... + +- <!-- .element: class="fragment" --> + all TVL contributors (some drive-by, some long-term contributors) +- <!-- .element: class="fragment" --> + countless Nix community members for their input on the architecture and rubberducking +- <!-- .element: class="fragment" --> + NLNET and others to sponsor parts of this + +---- + +# Questions? + +<style> +.container{ + display: flex; +} +.col{ + flex: 1; +} +</style> + +<div class="container"> + +<div class="col"> +Florian Klink / <a href="https://flokli.de">flokli.de</a><br /> +<img src="qrcode-flokli.svg" /> +</div> + +<div class="col"> +Tvix / <a href="https://tvix.dev">tvix.dev</a><br /> +<img src="qrcode-tvix.svg" /> +</div> + +</div> diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/tvix-store-graph.svg b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvix-store-graph.svg new file mode 100644 index 000000000000..56338b587e94 --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvix-store-graph.svg @@ -0,0 +1,17 @@ +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1297.1484375 836.19140625" width="1297.1484375" height="836.19140625"> + <!-- svg-source:excalidraw --> + + <defs> + <style class="style-fonts"> + @font-face { + font-family: "Virgil"; + src: url("https://excalidraw.com/Virgil.woff2"); + } + @font-face { + font-family: "Cascadia"; + src: url("https://excalidraw.com/Cascadia.woff2"); + } + </style> + + </defs> + <rect x="0" y="0" width="1297.1484375" height="836.19140625" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 64.05078125) rotate(0 71.10546875 23.685546875)"><path d="M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M1.58 10.65 C3.55 6.56, 7.08 5.83, 10.11 0.84 M1.58 10.65 C4.54 7.52, 5.83 5.33, 10.11 0.84 M1.97 16.29 C5.97 12.52, 7.97 9.89, 14.44 1.95 M1.97 16.29 C5.39 12.84, 8.96 8.01, 14.44 1.95 M1.71 22.69 C5.44 18.07, 11.5 12.49, 19.43 2.31 M1.71 22.69 C6.47 16.78, 11.69 12.31, 19.43 2.31 M1.45 29.09 C9.97 18.06, 21.19 8.77, 25.07 1.92 M1.45 29.09 C9.61 17.76, 19.35 7.56, 25.07 1.92 M1.85 34.73 C6.66 29.67, 14.83 20.48, 30.06 2.28 M1.85 34.73 C8.29 27.25, 17.05 18.03, 30.06 2.28 M2.24 40.37 C10.77 28.82, 20.92 18.16, 35.7 1.88 M2.24 40.37 C14.65 26.46, 25.75 13.13, 35.7 1.88 M3.29 45.26 C19.71 28.3, 32.79 9.79, 40.69 2.24 M3.29 45.26 C14.19 32.96, 23.82 22.41, 40.69 2.24 M6.97 47.13 C21.76 30.34, 38.17 12.66, 45.67 2.6 M6.97 47.13 C19.37 32.8, 32.58 18.66, 45.67 2.6 M12.61 46.74 C23.7 34.53, 32.71 23.85, 51.32 2.21 M12.61 46.74 C23.04 33.52, 34.64 21.81, 51.32 2.21 M17.6 47.1 C32.92 32.2, 44.82 17.61, 56.3 2.57 M17.6 47.1 C26.86 36.54, 34.43 27.02, 56.3 2.57 M22.58 47.46 C36.55 31.33, 51.38 11.55, 61.95 2.17 M22.58 47.46 C35.33 33.95, 46.66 20.15, 61.95 2.17 M28.23 47.06 C40.8 33.63, 55.34 16.98, 66.93 2.53 M28.23 47.06 C36.54 38.37, 43.25 29.02, 66.93 2.53 M33.21 47.42 C48.21 29.57, 61.74 14.8, 72.58 2.14 M33.21 47.42 C44.03 35.16, 55.8 21.76, 72.58 2.14 M38.86 47.03 C49.09 34.45, 58.15 26.51, 77.56 2.5 M38.86 47.03 C53.73 30.24, 67.18 15.29, 77.56 2.5 M43.84 47.39 C57.75 33, 73.31 14.12, 82.55 2.86 M43.84 47.39 C53.3 37.24, 61.17 27.55, 82.55 2.86 M49.49 46.99 C62.68 35.47, 70.45 21.6, 88.19 2.46 M49.49 46.99 C61.84 34.86, 73.11 21.46, 88.19 2.46 M54.47 47.35 C61.1 38.29, 69.25 27.83, 93.18 2.82 M54.47 47.35 C66.26 32.61, 76.99 20.02, 93.18 2.82 M60.12 46.96 C73.27 30.81, 84.64 16.32, 98.82 2.43 M60.12 46.96 C69.94 34.83, 80.66 21.92, 98.82 2.43 M65.1 47.32 C73.48 35.83, 86.65 26.47, 103.81 2.79 M65.1 47.32 C78.64 32.14, 91.97 18.02, 103.81 2.79 M70.09 47.68 C85.6 30.67, 98.3 12.05, 109.45 2.4 M70.09 47.68 C81.34 34.02, 94.32 21.42, 109.45 2.4 M75.73 47.28 C89.43 31.61, 102.84 18.22, 114.44 2.76 M75.73 47.28 C85.67 35.55, 96.35 22.65, 114.44 2.76 M80.72 47.64 C91.93 34.98, 103.63 17.52, 119.43 3.12 M80.72 47.64 C90.36 36.8, 98.88 24.6, 119.43 3.12 M86.36 47.25 C101.83 29.55, 117.32 12.7, 125.07 2.72 M86.36 47.25 C99.66 32.13, 111.08 17.18, 125.07 2.72 M91.35 47.61 C104.35 33.22, 117.8 19.95, 130.06 3.08 M91.35 47.61 C103.56 34.45, 114.2 20.27, 130.06 3.08 M96.99 47.21 C103.59 38.71, 113.94 28.96, 136.36 1.93 M96.99 47.21 C106.26 36.64, 117.24 24.44, 136.36 1.93 M101.98 47.57 C111.9 34.49, 126.09 20.88, 140.03 3.8 M101.98 47.57 C114.1 33.1, 128.09 16.65, 140.03 3.8 M106.97 47.93 C114.85 39.44, 121.62 28.8, 141.74 7.93 M106.97 47.93 C117.52 37.38, 125.94 26.56, 141.74 7.93 M112.61 47.54 C120.79 38.54, 126.18 28.87, 144.1 11.31 M112.61 47.54 C125.62 33.32, 137.05 19.84, 144.1 11.31 M117.6 47.9 C122.32 42.89, 130.44 32.85, 144.5 16.96 M117.6 47.9 C126.99 36.5, 135.5 27.33, 144.5 16.96 M123.24 47.51 C127.95 43.64, 132.17 36.33, 144.23 23.35 M123.24 47.51 C129.47 41.35, 136.09 32.69, 144.23 23.35 M128.23 47.87 C131.28 40.54, 137.28 36.59, 143.97 29.75 M128.23 47.87 C132.3 43.29, 136.94 37.23, 143.97 29.75 M132.56 48.98 C136.69 46.03, 136.69 42.77, 143.71 36.15 M132.56 48.98 C136.02 46.4, 138.73 42.29, 143.71 36.15" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M11.84 0 M11.84 0 C38.02 2.65, 61.84 0.28, 130.37 0 M11.84 0 C38.21 1.94, 65.76 1.5, 130.37 0 M130.37 0 C139.47 1, 143.75 5.88, 142.21 11.84 M130.37 0 C137.21 0.92, 139.99 4.87, 142.21 11.84 M142.21 11.84 C140.42 17.68, 141.72 25.2, 142.21 35.53 M142.21 11.84 C142.2 20.55, 141.47 28.9, 142.21 35.53 M142.21 35.53 C143.14 42.65, 138.92 45.76, 130.37 47.37 M142.21 35.53 C141.26 45.41, 136.07 48.58, 130.37 47.37 M130.37 47.37 C100.03 46.91, 70.33 46.17, 11.84 47.37 M130.37 47.37 C93.27 45.68, 57.65 45.47, 11.84 47.37 M11.84 47.37 C3.83 47.21, 0.04 43.58, 0 35.53 M11.84 47.37 C3.58 49.37, 2.09 44.62, 0 35.53 M0 35.53 C-0.11 25.56, 1.74 17.16, 0 11.84 M0 35.53 C-0.73 27.14, 0.25 16.41, 0 11.84 M0 11.84 C1.26 4.93, 5.18 0.97, 11.84 0 M0 11.84 C0.62 1.9, 3.42 2.05, 11.84 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(15 78.13632812499998) rotate(0 65.625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x01 0x02 0x03</text></g><g stroke-linecap="round" transform="translate(11.80078125 149.404296875) rotate(0 51.5 24.5)"><path d="M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M1.69 10.74 C4.79 9.22, 6.14 6.4, 10.22 0.93 M1.69 10.74 C4.82 7.44, 8.36 3.46, 10.22 0.93 M1.43 17.14 C5.08 10.94, 12.94 4.63, 14.55 2.05 M1.43 17.14 C7.42 12.34, 11.48 5.83, 14.55 2.05 M1.82 22.78 C7.79 17.76, 9.92 11.78, 20.19 1.65 M1.82 22.78 C6.91 17.54, 10.69 12.89, 20.19 1.65 M1.56 29.18 C10.68 19.58, 16.05 8.86, 25.18 2.01 M1.56 29.18 C6.66 24.1, 10.7 18.88, 25.18 2.01 M1.95 34.82 C14.06 20.7, 22.56 11.87, 30.16 2.37 M1.95 34.82 C12.05 23.95, 21.94 11.12, 30.16 2.37 M1.69 41.22 C7.98 31.97, 17.66 24.23, 35.81 1.98 M1.69 41.22 C14.67 26.87, 25.96 11.6, 35.81 1.98 M3.4 45.35 C12.42 31.5, 21.88 20.01, 40.79 2.34 M3.4 45.35 C14.79 32.81, 25.98 18.39, 40.79 2.34 M5.76 48.73 C19.03 33.54, 30.13 20.75, 46.44 1.94 M5.76 48.73 C15.95 35.19, 26.77 22.13, 46.44 1.94 M10.75 49.09 C25.08 35.85, 34.98 19.45, 51.42 2.3 M10.75 49.09 C25.36 34.29, 38.07 17.14, 51.42 2.3 M14.42 50.96 C25.24 34.64, 40.8 20.23, 57.07 1.91 M14.42 50.96 C31.56 31.52, 47.54 13.07, 57.07 1.91 M20.07 50.57 C36.58 31.78, 51.41 11.57, 62.05 2.27 M20.07 50.57 C35.27 32.37, 49.62 15.57, 62.05 2.27 M25.05 50.93 C33.69 40.39, 45.49 30.76, 67.7 1.87 M25.05 50.93 C39.57 35.58, 52.89 19.97, 67.7 1.87 M30.04 51.29 C42.51 36.46, 56.98 20.61, 72.68 2.23 M30.04 51.29 C39.08 40.34, 49.2 29.9, 72.68 2.23 M35.68 50.89 C47.44 39.9, 56.68 27.08, 78.33 1.84 M35.68 50.89 C52.05 33.69, 66.26 15.86, 78.33 1.84 M40.67 51.25 C53.32 36.02, 69.16 19.85, 83.31 2.2 M40.67 51.25 C53.19 37.93, 64.14 24.78, 83.31 2.2 M46.31 50.86 C62.11 32.31, 78.52 15.58, 88.96 1.8 M46.31 50.86 C57.68 37.06, 69.81 23.8, 88.96 1.8 M51.3 51.22 C59.29 38.98, 72.37 29.04, 93.94 2.16 M51.3 51.22 C64.73 35.51, 79.44 17.72, 93.94 2.16 M56.94 50.83 C69.81 34.76, 86.63 19.59, 98.93 2.52 M56.94 50.83 C72.69 32.66, 90.36 14.37, 98.93 2.52 M61.93 51.19 C77.07 35.49, 89.58 18.3, 101.95 5.15 M61.93 51.19 C76.05 33.49, 92.37 16.74, 101.95 5.15 M67.57 50.79 C76.42 41.59, 82.64 33.63, 103 10.04 M67.57 50.79 C76.25 40.25, 84.99 30.22, 103 10.04 M72.56 51.15 C79.09 44.86, 86.76 36.01, 103.4 15.68 M72.56 51.15 C78.4 43.41, 84.37 36.12, 103.4 15.68 M78.2 50.76 C84.15 41.95, 92.56 33.07, 103.13 22.08 M78.2 50.76 C88.29 39.34, 96.97 28.67, 103.13 22.08 M83.19 51.12 C88.53 45.31, 96.1 36.68, 103.53 27.72 M83.19 51.12 C91.24 41.6, 98.32 33.94, 103.53 27.72 M88.83 50.72 C90.5 47.29, 97.87 43.07, 103.27 34.12 M88.83 50.72 C94.68 44.06, 98.18 39.73, 103.27 34.12 M93.82 51.08 C96.56 47.12, 100.38 44.41, 104.97 38.25 M93.82 51.08 C96.61 47.17, 101.29 42.48, 104.97 38.25" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M12.25 0 M12.25 0 C28.51 0, 42.5 2.25, 90.75 0 M12.25 0 C32.14 -0.62, 52.39 1.04, 90.75 0 M90.75 0 C100.71 0.09, 101.8 3.77, 103 12.25 M90.75 0 C97.64 -0.83, 104 5.94, 103 12.25 M103 12.25 C101.33 19.82, 101.63 32.15, 103 36.75 M103 12.25 C102.35 21.13, 102.15 31.06, 103 36.75 M103 36.75 C104.56 46.87, 99.61 50.21, 90.75 49 M103 36.75 C104.15 46.69, 101.13 47.94, 90.75 49 M90.75 49 C62.94 48.31, 35.39 47.65, 12.25 49 M90.75 49 C67.73 49.76, 43.51 49.69, 12.25 49 M12.25 49 C3.6 47.48, -1.71 45.84, 0 36.75 M12.25 49 C3.19 49.75, -1.85 43.96, 0 36.75 M0 36.75 C-0.6 25.33, 0.5 18.11, 0 12.25 M0 36.75 C-0.07 30.3, -1.01 22.9, 0 12.25 M0 12.25 C-1.34 4.43, 2.32 -0.12, 12.25 0 M0 12.25 C-0.18 4.12, 4.26 -0.37, 12.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(16.80078125 164.30429687499998) rotate(0 42.1875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x04 0x05</text></g><g stroke-linecap="round" transform="translate(14.93359375 236.67578125) rotate(0 4.138671875 25.552734375)"><path d="M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M0.11 8.04 C1.82 6.13, 2.96 4.61, 4.7 2.76 M0.11 8.04 C1.05 6.92, 2.71 5.33, 4.7 2.76 M-0.15 14.44 C2.61 10.36, 4.52 9.27, 9.69 3.12 M-0.15 14.44 C3 10.45, 7.79 5.81, 9.69 3.12 M0.24 20.08 C1.78 17.33, 6.21 13.86, 9.43 9.52 M0.24 20.08 C3.9 16.52, 6.5 12.39, 9.43 9.52 M-0.02 26.48 C2.46 23.23, 7.19 20.21, 9.82 15.16 M-0.02 26.48 C2.52 23.58, 5.92 19.96, 9.82 15.16 M-0.28 32.88 C3.43 29.78, 4.96 26.51, 9.56 21.56 M-0.28 32.88 C1.68 30.37, 4.75 26.47, 9.56 21.56 M0.11 38.52 C2.43 35.55, 6.84 32.06, 9.3 27.96 M0.11 38.52 C3.34 34.69, 7.79 30.27, 9.3 27.96 M-0.15 44.92 C4.41 39.72, 7.29 36.44, 9.69 33.6 M-0.15 44.92 C2.92 41.4, 5.54 37.99, 9.69 33.6 M0.9 49.81 C2.58 47.91, 5.61 43.76, 9.43 40 M0.9 49.81 C3.14 46.67, 5.91 43.78, 9.43 40 M3.92 52.43 C5.01 50.49, 6.18 49.02, 9.82 45.64 M3.92 52.43 C5.89 50.77, 7.52 48.28, 9.82 45.64" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M2.07 0 M2.07 0 C2.97 -0.21, 4.11 0.12, 6.21 0 M2.07 0 C3.19 -0.16, 4.23 0.11, 6.21 0 M6.21 0 C8.97 0.45, 9.01 1.39, 8.28 2.07 M6.21 0 C9.57 -1.98, 9.89 2.6, 8.28 2.07 M8.28 2.07 C8.66 12.58, 9.73 22.31, 8.28 49.04 M8.28 2.07 C8.53 15.26, 7.49 28.86, 8.28 49.04 M8.28 49.04 C6.95 49.38, 7.58 52.75, 6.21 51.11 M8.28 49.04 C10.35 52.7, 9.64 53.24, 6.21 51.11 M6.21 51.11 C5.12 51.08, 3.76 50.9, 2.07 51.11 M6.21 51.11 C5.14 51.07, 4.25 51.01, 2.07 51.11 M2.07 51.11 C2.45 52.5, -1.17 52.21, 0 49.04 M2.07 51.11 C-1.47 50.3, 2.23 51.76, 0 49.04 M0 49.04 C-0.13 38.39, -0.48 26.48, 0 2.07 M0 49.04 C-1.31 32.15, -0.35 13.45, 0 2.07 M0 2.07 C0.41 0.36, -0.67 -0.56, 2.07 0 M0 2.07 C-0.35 -0.58, -1.03 -1.54, 2.07 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(10.23046875 10) rotate(0 23.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Blobs</text></g><g transform="translate(279.12890625 12.759374999999977) rotate(0 51.5625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Directories</text></g><g stroke-linecap="round" transform="translate(283.875 68.3828125) rotate(0 197 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.28 14.49, 8.49 11.04, 18.1 2.48 M3.66 19.09 C7.7 14.57, 11.52 11.66, 18.1 2.48 M2.75 26.24 C11.51 19.05, 17.92 11.98, 24.4 1.33 M2.75 26.24 C7.23 20.55, 12.55 15.89, 24.4 1.33 M2.48 32.64 C12.12 24.05, 19.28 13.72, 30.7 0.18 M2.48 32.64 C9.16 24.56, 14.83 17.39, 30.7 0.18 M2.88 38.28 C11.43 28.28, 22.31 16.44, 34.37 2.05 M2.88 38.28 C14.6 24.25, 27.89 9.94, 34.37 2.05 M2.62 44.68 C9.32 34.89, 17.39 26.88, 39.36 2.41 M2.62 44.68 C11.95 33.82, 21.66 21.02, 39.36 2.41 M2.36 51.07 C12.81 41.21, 19.43 27.76, 45 2.02 M2.36 51.07 C16.68 33.29, 30.46 16.46, 45 2.02 M2.75 56.72 C16.64 41.98, 28.98 26.21, 49.99 2.38 M2.75 56.72 C21 35.95, 39.52 14.81, 49.99 2.38 M2.49 63.11 C19.63 44.26, 39.08 21.21, 55.63 1.98 M2.49 63.11 C17.01 46.73, 31.61 30.35, 55.63 1.98 M2.88 68.76 C22.9 46.33, 43.08 21.26, 60.62 2.34 M2.88 68.76 C23.19 45.39, 41.88 24.57, 60.62 2.34 M2.62 75.16 C23.83 54.32, 39.89 29.17, 66.26 1.95 M2.62 75.16 C18.47 56.13, 34.46 38.2, 66.26 1.95 M2.36 81.55 C29.87 50.88, 54.51 22.49, 71.25 2.31 M2.36 81.55 C26.5 54.38, 49.03 26.57, 71.25 2.31 M2.76 87.2 C25.76 61.24, 52.69 29.56, 76.89 1.91 M2.76 87.2 C22.68 64.01, 42.85 40.22, 76.89 1.91 M2.49 93.59 C30.49 61.54, 57.87 26.06, 81.88 2.27 M2.49 93.59 C31.33 60.52, 59.32 27.03, 81.88 2.27 M2.89 99.24 C28.16 70.88, 49.29 44.38, 87.52 1.88 M2.89 99.24 C22.19 77.54, 40.48 55.68, 87.52 1.88 M2.63 105.64 C27.42 78.52, 50.89 50.01, 92.51 2.24 M2.63 105.64 C38.6 64.43, 74.48 23.22, 92.51 2.24 M2.37 112.03 C35.69 71.87, 67.49 34.66, 98.15 1.85 M2.37 112.03 C29.87 80.37, 57.31 48.8, 98.15 1.85 M2.76 117.68 C35.35 78.95, 70.62 41.89, 103.14 2.21 M2.76 117.68 C24.16 94.43, 44.43 69.26, 103.14 2.21 M3.16 123.32 C27.31 95.67, 53.64 64.97, 108.78 1.81 M3.16 123.32 C40.3 79.27, 78.36 37.25, 108.78 1.81 M4.86 127.45 C35.22 90.31, 66.42 52.47, 113.77 2.17 M4.86 127.45 C27.89 100.07, 53.15 74.47, 113.77 2.17 M5.26 133.1 C41.54 89.35, 82.58 48.1, 119.41 1.78 M5.26 133.1 C50.78 80.9, 96.57 28.36, 119.41 1.78 M7.62 136.47 C50.54 85.68, 96.45 32.28, 124.4 2.14 M7.62 136.47 C50.54 87.26, 95.13 37.29, 124.4 2.14 M10.64 139.1 C44.47 96.28, 83.89 53.02, 130.04 1.74 M10.64 139.1 C49.89 96.71, 86.98 53.36, 130.04 1.74 M14.31 140.97 C55.74 92.08, 97.64 42.48, 135.03 2.1 M14.31 140.97 C49.36 97.94, 86.03 55.71, 135.03 2.1 M17.33 143.59 C62.88 90.12, 107.49 38.88, 140.67 1.71 M17.33 143.59 C52.29 102.09, 87.68 60.93, 140.67 1.71 M21.01 145.46 C48.28 114.13, 76.95 81.48, 145.66 2.07 M21.01 145.46 C64.61 95.84, 107.71 47.87, 145.66 2.07 M25.34 146.58 C73.23 89.86, 125.31 35.24, 150.65 2.43 M25.34 146.58 C51.9 117.94, 78.32 86.29, 150.65 2.43 M32.29 144.67 C78.07 95.21, 118.24 44.56, 156.29 2.03 M32.29 144.67 C74.54 98.13, 114.59 49.94, 156.29 2.03 M37.28 145.03 C85.09 88.58, 133.69 36.74, 161.28 2.39 M37.28 145.03 C69.44 108.12, 100.26 71.2, 161.28 2.39 M42.92 144.64 C90.72 91.41, 136.95 40.49, 166.92 2 M42.92 144.64 C66.95 114.58, 93.7 85.42, 166.92 2 M47.91 145 C75.02 114.35, 102.25 80.22, 171.91 2.36 M47.91 145 C87.4 99.39, 126.35 54.03, 171.91 2.36 M52.9 145.36 C79.04 113.6, 107.32 79.81, 177.55 1.96 M52.9 145.36 C77.61 115.94, 103.48 86, 177.55 1.96 M58.54 144.96 C92.12 105.3, 128.8 63.9, 182.54 2.32 M58.54 144.96 C106.61 90.51, 152.9 36.35, 182.54 2.32 M63.53 145.32 C107.47 99.27, 149.03 48.65, 188.18 1.93 M63.53 145.32 C96.13 109.61, 126.87 72.99, 188.18 1.93 M69.17 144.93 C102.47 104.11, 137.86 61.81, 193.17 2.29 M69.17 144.93 C110.04 98.38, 152.23 51.84, 193.17 2.29 M74.16 145.29 C104.78 108.19, 138.63 71.58, 198.81 1.9 M74.16 145.29 C99.31 117.03, 123.84 87.71, 198.81 1.9 M79.8 144.9 C130.71 87.32, 178.54 30.53, 203.8 2.26 M79.8 144.9 C124.29 94.42, 167.51 43.91, 203.8 2.26 M84.79 145.26 C111.41 111.48, 143.37 80.45, 209.44 1.86 M84.79 145.26 C130.36 93.05, 176.97 39.33, 209.44 1.86 M90.43 144.86 C128.08 97.62, 168.49 56.08, 214.43 2.22 M90.43 144.86 C130.35 96.04, 171.08 48.72, 214.43 2.22 M95.42 145.22 C146.19 90.39, 192.4 31.56, 220.07 1.83 M95.42 145.22 C120.72 114.85, 146.9 84.4, 220.07 1.83 M101.06 144.83 C127.22 111.52, 152.8 83.04, 225.06 2.19 M101.06 144.83 C132.86 108.13, 163.34 71.68, 225.06 2.19 M106.05 145.19 C154.17 92.56, 198.8 39.31, 230.7 1.79 M106.05 145.19 C136.53 110.59, 166.08 76.75, 230.7 1.79 M111.69 144.79 C138.65 117.15, 162.06 85.81, 235.69 2.15 M111.69 144.79 C137.61 113.65, 164.22 81.98, 235.69 2.15 M116.68 145.15 C157.06 99.25, 194.58 56.02, 241.33 1.76 M116.68 145.15 C155.1 99.61, 194.87 53.74, 241.33 1.76 M122.32 144.76 C155.38 104.12, 188.9 66.56, 246.32 2.12 M122.32 144.76 C171.19 89.94, 218.89 34.94, 246.32 2.12 M127.31 145.12 C160.63 107.75, 195.41 68.39, 251.96 1.72 M127.31 145.12 C171.79 94.11, 216.13 43.25, 251.96 1.72 M132.95 144.72 C167.14 104.52, 206.3 64.21, 256.95 2.08 M132.95 144.72 C164.99 105.52, 198.48 68.92, 256.95 2.08 M137.94 145.08 C183.71 92.31, 226.45 45.7, 262.59 1.69 M137.94 145.08 C163.89 115.91, 190.22 85.14, 262.59 1.69 M143.58 144.69 C174.92 109.48, 206.89 70.46, 267.58 2.05 M143.58 144.69 C170.4 113.21, 199.74 82.17, 267.58 2.05 M148.57 145.05 C178.08 111.76, 203.33 81.73, 272.56 2.41 M148.57 145.05 C173.64 115.53, 198.59 86.39, 272.56 2.41 M154.21 144.65 C187.55 108.07, 219.55 68.66, 278.21 2.01 M154.21 144.65 C200.06 91.97, 243.47 39.89, 278.21 2.01 M159.2 145.01 C186.66 112.8, 212.53 85.21, 283.19 2.37 M159.2 145.01 C207.12 89.92, 255.48 37.14, 283.19 2.37 M164.19 145.37 C202.12 104.92, 237.2 59.79, 288.84 1.98 M164.19 145.37 C194.82 106.77, 226.84 70.45, 288.84 1.98 M169.83 144.98 C212.12 93.05, 258.35 43.46, 293.82 2.34 M169.83 144.98 C201.44 106.46, 235.81 67.61, 293.82 2.34 M174.82 145.34 C207.54 111.65, 235.58 74.76, 299.47 1.94 M174.82 145.34 C199.92 118.44, 225.68 88.79, 299.47 1.94 M180.46 144.94 C208.5 116.42, 230.68 85.13, 304.45 2.3 M180.46 144.94 C218.97 100.78, 259.51 55.75, 304.45 2.3 M185.45 145.31 C221.24 102.72, 256.86 59.74, 310.1 1.91 M185.45 145.31 C232.8 92.21, 280.33 37.72, 310.1 1.91 M191.09 144.91 C238.27 88.19, 288.2 35.33, 315.08 2.27 M191.09 144.91 C220.59 109.55, 252.22 74.41, 315.08 2.27 M196.08 145.27 C239.76 94.85, 284.75 46.35, 320.73 1.88 M196.08 145.27 C230.49 104.94, 265.25 68.33, 320.73 1.88 M201.72 144.88 C234.66 108.79, 263.22 71.79, 325.71 2.24 M201.72 144.88 C237.32 101.42, 273.7 59.07, 325.71 2.24 M206.71 145.24 C253.21 90.64, 300.99 37.76, 331.36 1.84 M206.71 145.24 C240.65 108.22, 273.71 69.33, 331.36 1.84 M212.35 144.84 C242.19 114.5, 267.67 79.17, 336.34 2.2 M212.35 144.84 C236.6 115.07, 262.16 86.57, 336.34 2.2 M217.34 145.2 C251.25 105.29, 286.42 64.08, 341.99 1.81 M217.34 145.2 C249.04 108.05, 278.75 71.6, 341.99 1.81 M222.98 144.81 C254.78 109.71, 284.95 73.51, 346.97 2.17 M222.98 144.81 C251.44 112.17, 277.38 81.44, 346.97 2.17 M227.97 145.17 C258.67 109.25, 290.39 71.64, 352.62 1.77 M227.97 145.17 C253.46 115.85, 279.91 85.09, 352.62 1.77 M233.61 144.77 C279.92 90.22, 328.36 36.09, 357.6 2.13 M233.61 144.77 C263.54 109.84, 296.58 72.44, 357.6 2.13 M238.6 145.13 C284.33 94.02, 329.86 38.18, 363.25 1.74 M238.6 145.13 C270.6 110.97, 300.1 77.3, 363.25 1.74 M244.24 144.74 C285.03 98.47, 325.99 49.52, 368.23 2.1 M244.24 144.74 C288.81 94.28, 333.65 43.13, 368.23 2.1 M249.23 145.1 C274.07 113.24, 304.32 83.42, 373.22 2.46 M249.23 145.1 C288.32 102.57, 326.71 58.15, 373.22 2.46 M254.87 144.7 C297.9 94.29, 343.78 43.11, 377.55 3.57 M254.87 144.7 C286.66 106.11, 320.39 66.72, 377.55 3.57 M259.86 145.06 C295.8 103.74, 331.06 61.72, 381.23 5.44 M259.86 145.06 C298.82 101.67, 336.82 56.01, 381.23 5.44 M265.5 144.67 C313.19 90.52, 361.44 33.85, 384.9 7.31 M265.5 144.67 C292.92 114.87, 319.46 83.51, 384.9 7.31 M270.49 145.03 C293.76 118.67, 317.19 90.91, 387.92 9.94 M270.49 145.03 C298.6 112.33, 327.42 78.58, 387.92 9.94 M276.13 144.63 C309.18 107.62, 339.56 70.71, 390.28 13.32 M276.13 144.63 C314.84 98.86, 353.06 54.55, 390.28 13.32 M281.12 144.99 C305.94 116.7, 328.37 88.76, 391.99 17.45 M281.12 144.99 C321.03 97.29, 362.22 51.33, 391.99 17.45 M286.1 145.35 C324.81 103.13, 365.35 57.02, 393.04 22.34 M286.1 145.35 C313.97 112.26, 343.79 77.31, 393.04 22.34 M291.75 144.96 C324.16 107.97, 358.78 71.44, 395.4 25.72 M291.75 144.96 C322.28 110.33, 352.3 74.13, 395.4 25.72 M296.73 145.32 C322.53 113.82, 350.61 86.17, 395.14 32.11 M296.73 145.32 C330.9 104.68, 367.32 64.68, 395.14 32.11 M302.38 144.93 C320.38 122.95, 340.8 101.95, 395.54 37.76 M302.38 144.93 C328.33 112.21, 356.43 82.25, 395.54 37.76 M307.36 145.29 C337.92 110.64, 366.55 76.31, 395.28 44.15 M307.36 145.29 C325.37 123.43, 344.42 100.44, 395.28 44.15 M313.01 144.89 C335.45 120.95, 355.71 93.18, 395.01 50.55 M313.01 144.89 C342.22 110.33, 372.53 76.59, 395.01 50.55 M317.99 145.25 C335.46 125.76, 350.14 107.98, 395.41 56.2 M317.99 145.25 C334.45 126.54, 349.07 108.69, 395.41 56.2 M323.64 144.86 C350.88 112.04, 380.82 81.41, 395.15 62.59 M323.64 144.86 C349.73 114.44, 376.12 84.65, 395.15 62.59 M328.62 145.22 C342.07 126.15, 358.11 110, 395.54 68.24 M328.62 145.22 C352.38 118.21, 373.64 91.69, 395.54 68.24 M334.27 144.82 C356.58 116.53, 380.06 91.2, 395.28 74.63 M334.27 144.82 C352.85 123.85, 373.24 101.61, 395.28 74.63 M339.25 145.18 C350.58 130.51, 366.65 115.79, 395.67 80.28 M339.25 145.18 C360.84 120.94, 381.57 97.17, 395.67 80.28 M344.9 144.79 C359.95 129.93, 374.97 110.51, 395.41 86.68 M344.9 144.79 C359.66 128.95, 373.84 112.99, 395.41 86.68 M349.88 145.15 C365.74 124.35, 384.39 105.92, 395.81 92.32 M349.88 145.15 C365.01 128.41, 378.61 111.44, 395.81 92.32 M355.53 144.75 C366.83 128.33, 381.85 115.58, 395.55 98.72 M355.53 144.75 C366.51 134.09, 376.57 121.06, 395.55 98.72 M360.51 145.11 C371.57 133.46, 379.69 124.34, 395.94 104.36 M360.51 145.11 C371.82 131.23, 384.33 117.41, 395.94 104.36 M364.19 146.98 C372.36 139.23, 375.92 131.75, 395.68 110.76 M364.19 146.98 C375.25 134.47, 383.71 124.14, 395.68 110.76 M370.49 145.83 C377.43 139.68, 383.18 129.21, 396.73 115.65 M370.49 145.83 C377.33 137.24, 382.58 130.01, 396.73 115.65 M377.44 143.93 C380.98 139.22, 385.26 134.38, 391.88 127.33 M377.44 143.93 C381.01 139.93, 385.77 134.2, 391.88 127.33" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C158.41 -0.65, 283.84 1.37, 362 0 M32 0 C99.31 -2.94, 165.01 -1.66, 362 0 M362 0 C382.37 -1.19, 392.06 11.27, 394 32 M362 0 C383.53 0.55, 395.84 12.5, 394 32 M394 32 C396.17 49.29, 394.95 72.1, 394 113 M394 32 C394.74 57.51, 393.79 80.56, 394 113 M394 113 C393.49 135.83, 381.87 143.9, 362 145 M394 113 C391.98 135.04, 383.6 143.19, 362 145 M362 145 C291.31 144.27, 221.15 142.78, 32 145 M362 145 C249.93 143.31, 138.97 142.96, 32 145 M32 145 C8.71 146.69, -1.86 135.95, 0 113 M32 145 C10.21 143.8, 1.65 132.18, 0 113 M0 113 C0.55 94.72, -1.35 75.26, 0 32 M0 113 C-0.88 90.36, -1.5 66.27, 0 32 M0 32 C1.9 12.2, 9.12 -1.48, 32 0 M0 32 C0.22 11.69, 10.1 1.96, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(288.875 73.3828125) rotate(0 140.625 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <empty-blob-digest></text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 0</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> executable: false</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(279.59193843887 271.159696266393) rotate(0 198.99999999999994 130)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C7.06 13.81, 9.24 9.34, 18.1 2.48 M3.66 19.09 C8.44 11.73, 13.76 5.27, 18.1 2.48 M2.75 26.24 C10 16.18, 18.61 6.13, 24.4 1.33 M2.75 26.24 C9.44 19.31, 14.57 12.4, 24.4 1.33 M2.48 32.64 C10.34 21.03, 17.74 12.3, 30.7 0.18 M2.48 32.64 C13.49 21.44, 21.83 9.48, 30.7 0.18 M2.88 38.28 C12.91 25, 26.86 13.76, 34.37 2.05 M2.88 38.28 C12.64 28.15, 21.63 16.63, 34.37 2.05 M2.62 44.68 C14.17 33.27, 22.89 18.23, 39.36 2.41 M2.62 44.68 C12.44 34.48, 21.24 23.55, 39.36 2.41 M2.36 51.07 C18.27 36.19, 31.27 19.8, 45 2.02 M2.36 51.07 C15.34 37.29, 27.12 23.41, 45 2.02 M2.75 56.72 C18.22 37.56, 35.93 17.94, 49.99 2.38 M2.75 56.72 C20.57 34.56, 37.8 14.24, 49.99 2.38 M2.49 63.11 C18.51 41.78, 36.66 23.69, 55.63 1.98 M2.49 63.11 C16.06 46.84, 30.13 32.58, 55.63 1.98 M2.23 69.51 C14.08 54.4, 27.81 42.3, 60.62 2.34 M2.23 69.51 C14.8 54.54, 28.11 38.47, 60.62 2.34 M2.62 75.16 C23.22 53.14, 45.11 28.68, 66.26 1.95 M2.62 75.16 C18.68 55.49, 35.76 36.87, 66.26 1.95 M2.36 81.55 C29.49 51.06, 52.67 20.07, 71.25 2.31 M2.36 81.55 C23.35 59.17, 43.86 35.46, 71.25 2.31 M2.76 87.2 C29.08 59.14, 54.26 30.38, 76.89 1.91 M2.76 87.2 C25.2 61.93, 47.39 35.45, 76.89 1.91 M2.49 93.59 C22.97 73.16, 41.24 50.49, 81.88 2.27 M2.49 93.59 C22.85 70.13, 41.2 48.51, 81.88 2.27 M2.23 99.99 C26.73 70.85, 53 42.34, 87.52 1.88 M2.23 99.99 C26.11 70.65, 52.18 43.37, 87.52 1.88 M2.63 105.64 C29.25 73.43, 57.28 44.11, 92.51 2.24 M2.63 105.64 C22.21 84.02, 40.09 61.9, 92.51 2.24 M2.37 112.03 C24.78 84.92, 46.32 60.61, 98.15 1.85 M2.37 112.03 C40.71 68.73, 77.43 25.35, 98.15 1.85 M2.1 118.43 C32.45 81.76, 64.34 45.79, 103.14 2.21 M2.1 118.43 C26.04 90.13, 50.16 63.23, 103.14 2.21 M2.5 124.07 C34.35 88.85, 66.58 53.31, 108.78 1.81 M2.5 124.07 C35.8 86.3, 67.38 48.42, 108.78 1.81 M2.24 130.47 C44.13 79.64, 85.07 32.68, 113.77 2.17 M2.24 130.47 C28.26 100.65, 54.41 68.41, 113.77 2.17 M2.63 136.11 C39.83 96.08, 73.86 52.5, 119.41 1.78 M2.63 136.11 C27.92 105.97, 55.2 76.2, 119.41 1.78 M2.37 142.51 C38.64 103.62, 72.54 62.27, 124.4 2.14 M2.37 142.51 C38.27 104.7, 71.42 64.68, 124.4 2.14 M2.11 148.91 C31.62 115.14, 60.21 82.46, 130.04 1.74 M2.11 148.91 C30.64 115.22, 60.37 82.49, 130.04 1.74 M2.5 154.55 C45.43 105.72, 87.93 57.79, 135.03 2.1 M2.5 154.55 C39.77 111.59, 78.21 68.92, 135.03 2.1 M2.24 160.95 C40.01 120.72, 75.93 77.72, 140.67 1.71 M2.24 160.95 C44.66 112.93, 87.03 64.56, 140.67 1.71 M1.98 167.35 C39.14 124.53, 74.75 81.57, 145.66 2.07 M1.98 167.35 C47.85 117.91, 91.41 66.21, 145.66 2.07 M2.38 172.99 C58.38 106.95, 115.31 43.43, 150.65 2.43 M2.38 172.99 C53.11 115.34, 104.81 56.32, 150.65 2.43 M2.11 179.39 C64.27 108.42, 124.58 39.52, 156.29 2.03 M2.11 179.39 C42.26 131.15, 83.61 83.43, 156.29 2.03 M2.51 185.03 C62.42 116.85, 122.68 43.77, 161.28 2.39 M2.51 185.03 C56.66 122.43, 112.32 58.31, 161.28 2.39 M2.25 191.43 C51.58 133.03, 102.03 75.72, 166.92 2 M2.25 191.43 C38.3 149.45, 75.45 107.32, 166.92 2 M1.99 197.83 C42.68 152.88, 80.66 108.12, 171.91 2.36 M1.99 197.83 C66.15 123.85, 132.83 47.33, 171.91 2.36 M2.38 203.47 C55.91 143.98, 107.7 83.76, 177.55 1.96 M2.38 203.47 C56.81 142.73, 110.46 81.58, 177.55 1.96 M2.12 209.87 C66.55 137, 131.82 62.14, 182.54 2.32 M2.12 209.87 C50.61 157.03, 98.73 101.44, 182.54 2.32 M1.86 216.27 C63.05 142.96, 126.25 69.69, 188.18 1.93 M1.86 216.27 C65.44 140.95, 131.03 66.87, 188.18 1.93 M2.25 221.91 C59.05 161.57, 111.38 97.22, 193.17 2.29 M2.25 221.91 C52.49 164.23, 101.5 106.36, 193.17 2.29 M1.33 229.06 C64.26 156.57, 124.34 84.79, 198.81 1.9 M1.33 229.06 C60.97 162.1, 120.98 94.62, 198.81 1.9 M1.73 234.71 C54.24 174.7, 106.54 116.97, 203.8 2.26 M1.73 234.71 C76.5 149.79, 152.59 63.83, 203.8 2.26 M2.78 239.59 C49.77 186.82, 94.95 134.94, 209.44 1.86 M2.78 239.59 C83.35 146.44, 163.63 53.44, 209.44 1.86 M4.49 243.73 C48.55 190.47, 93.86 139.53, 214.43 2.22 M4.49 243.73 C63.53 174.61, 122.23 106.2, 214.43 2.22 M6.19 247.86 C77.48 165.73, 149.04 81.52, 220.07 1.83 M6.19 247.86 C85.41 157.55, 164.08 66.86, 220.07 1.83 M8.56 251.24 C90.22 155.68, 175.16 56.51, 225.06 2.19 M8.56 251.24 C89.12 157.29, 172.43 62.13, 225.06 2.19 M10.92 254.62 C69.41 191.54, 124.13 126, 230.7 1.79 M10.92 254.62 C69.41 187.27, 127.03 120.42, 230.7 1.79 M13.94 257.24 C82.45 176.73, 153.61 97.39, 235.69 2.15 M13.94 257.24 C93.45 166.82, 173.91 75.69, 235.69 2.15 M18.27 258.36 C76.98 194.1, 131.53 129.14, 241.33 1.76 M18.27 258.36 C66.67 201.61, 115.46 145.37, 241.33 1.76 M22.6 259.47 C75.95 201, 128.47 140.06, 246.32 2.12 M22.6 259.47 C70.35 207.82, 116.17 155.22, 246.32 2.12 M26.27 261.34 C95.93 180.59, 169.95 95.39, 251.96 1.72 M26.27 261.34 C89.37 190.53, 152.1 118.22, 251.96 1.72 M32.57 260.19 C96.06 189.84, 155.98 120.97, 256.95 2.08 M32.57 260.19 C78.18 205.29, 124.7 151.48, 256.95 2.08 M37.56 260.55 C117 171.22, 192.42 83.99, 262.59 1.69 M37.56 260.55 C121.96 163.6, 206.2 67.19, 262.59 1.69 M43.2 260.16 C105.28 189.09, 169.45 117.92, 267.58 2.05 M43.2 260.16 C123.84 168.01, 203.45 76.43, 267.58 2.05 M48.19 260.52 C117.81 178.99, 186.9 100.6, 272.56 2.41 M48.19 260.52 C125.53 171.84, 202.4 82.45, 272.56 2.41 M53.83 260.12 C138.08 162.28, 222.76 67.81, 278.21 2.01 M53.83 260.12 C128.55 171.85, 204.35 84.41, 278.21 2.01 M58.82 260.48 C127.06 184.42, 193 106.58, 283.19 2.37 M58.82 260.48 C135.91 173.81, 211.1 87.23, 283.19 2.37 M64.46 260.09 C109.71 206.88, 156.75 154.77, 288.84 1.98 M64.46 260.09 C142.84 169.53, 220.7 80.3, 288.84 1.98 M69.45 260.45 C138.09 183.73, 208 105.35, 293.82 2.34 M69.45 260.45 C156.6 162.77, 241.99 64.65, 293.82 2.34 M75.09 260.06 C143.01 182.6, 211.21 103.49, 299.47 1.94 M75.09 260.06 C136 192, 195.87 123.37, 299.47 1.94 M80.08 260.42 C126.17 206.79, 172.95 151.57, 304.45 2.3 M80.08 260.42 C129.25 204.69, 179.83 146.37, 304.45 2.3 M85.72 260.02 C157.76 178.28, 226.22 97.47, 310.1 1.91 M85.72 260.02 C149.05 184.68, 213.67 111.24, 310.1 1.91 M90.71 260.38 C181.48 157.9, 269.25 55.91, 315.08 2.27 M90.71 260.38 C157.59 183.34, 224.55 106.5, 315.08 2.27 M95.7 260.74 C174.5 168.64, 255.03 76.7, 320.73 1.88 M95.7 260.74 C164.52 183.36, 232.23 105.12, 320.73 1.88 M101.34 260.35 C181.6 167.21, 261.15 73.9, 325.71 2.24 M101.34 260.35 C177.77 172.29, 255.39 82.95, 325.71 2.24 M106.33 260.71 C168.93 189.98, 230.06 115.81, 331.36 1.84 M106.33 260.71 C170.53 184.03, 237.17 107.92, 331.36 1.84 M111.97 260.31 C176.35 190.63, 236.85 119.43, 336.34 2.2 M111.97 260.31 C157.57 208.21, 204.38 154.93, 336.34 2.2 M116.96 260.67 C164.3 205.68, 212.63 148.72, 341.99 1.81 M116.96 260.67 C197.94 169.46, 278.58 77.5, 341.99 1.81 M122.6 260.28 C175.27 200.79, 229.84 138.31, 346.97 2.17 M122.6 260.28 C172.5 202.83, 220.76 146.35, 346.97 2.17 M127.59 260.64 C215.15 158.17, 304.42 54.77, 352.62 1.77 M127.59 260.64 C188.96 189.82, 250.22 118.28, 352.62 1.77 M133.23 260.24 C183.32 201.93, 235.94 141.72, 357.6 2.13 M133.23 260.24 C184.28 203.09, 233.15 145.34, 357.6 2.13 M138.22 260.6 C220.44 165.6, 301.95 72.93, 363.25 1.74 M138.22 260.6 C215.16 172.58, 291.69 83.91, 363.25 1.74 M143.86 260.21 C216.13 172.96, 292.05 87.04, 368.23 2.1 M143.86 260.21 C223.54 169.38, 302.36 80.03, 368.23 2.1 M148.85 260.57 C228.87 169.81, 309.22 79.04, 374.53 0.95 M148.85 260.57 C225.85 169.3, 304.04 78.63, 374.53 0.95 M154.49 260.17 C230.89 172.65, 306.45 86.57, 378.21 2.82 M154.49 260.17 C210.94 195.8, 265.6 130.92, 378.21 2.82 M159.48 260.53 C223.94 184.22, 290.75 110.57, 382.54 3.93 M159.48 260.53 C244.05 163.85, 329.27 67.22, 382.54 3.93 M165.12 260.14 C236.95 176.83, 309.56 92.2, 386.87 5.05 M165.12 260.14 C221.23 195.95, 279.16 130.62, 386.87 5.05 M170.11 260.5 C221.21 201.49, 273.24 142.28, 389.23 8.43 M170.11 260.5 C218.37 205.87, 266.44 150.41, 389.23 8.43 M175.75 260.11 C241.58 187.82, 304.45 113.61, 392.25 11.05 M175.75 260.11 C222.69 207.98, 267.39 155.95, 392.25 11.05 M180.74 260.47 C245.33 188.58, 307.54 115.78, 395.27 13.68 M180.74 260.47 C261.6 168.2, 340.34 76.88, 395.27 13.68 M186.38 260.07 C251.57 186.65, 319.28 107.6, 396.32 18.56 M186.38 260.07 C236.65 200.09, 289.45 140.81, 396.32 18.56 M191.37 260.43 C255.8 183.29, 323.42 107.08, 397.37 23.45 M191.37 260.43 C233.86 209.7, 277.09 160.88, 397.37 23.45 M197.01 260.04 C273.9 173.76, 350.14 83.39, 397.77 29.09 M197.01 260.04 C238.49 210.28, 280.91 161.3, 397.77 29.09 M202 260.4 C249.95 204.58, 301.21 145.35, 399.47 33.23 M202 260.4 C246.27 207.11, 292.1 154.9, 399.47 33.23 M207.64 260 C282.04 175.37, 353.03 93.88, 399.21 39.63 M207.64 260 C252.23 206.89, 298.22 154.54, 399.21 39.63 M212.63 260.36 C270.79 190.87, 332.59 125.04, 399.61 45.27 M212.63 260.36 C255.76 212.2, 297.66 164.88, 399.61 45.27 M217.62 260.72 C270.87 200.01, 324.39 137.32, 399.34 51.67 M217.62 260.72 C286.2 182.97, 352.23 105.9, 399.34 51.67 M223.26 260.33 C266.88 209.47, 311.98 157.05, 399.74 57.31 M223.26 260.33 C270.48 203.33, 319.36 147.73, 399.74 57.31 M228.25 260.69 C264.48 217.85, 298.81 176.68, 399.48 63.71 M228.25 260.69 C287.06 192.75, 345.09 124.31, 399.48 63.71 M233.89 260.29 C283.82 202.81, 333.35 147.36, 399.22 70.11 M233.89 260.29 C296.32 189.36, 357.95 117.97, 399.22 70.11 M238.88 260.65 C288.24 201.51, 340.4 144.75, 399.61 75.75 M238.88 260.65 C302.98 187.65, 365.83 115.51, 399.61 75.75 M244.52 260.26 C301.41 192.57, 362.82 123.97, 399.35 82.15 M244.52 260.26 C276.87 221.94, 309.71 183.55, 399.35 82.15 M249.51 260.62 C294.86 210.74, 338.19 159.76, 399.74 87.79 M249.51 260.62 C288.68 213.68, 329.48 168.33, 399.74 87.79 M255.15 260.22 C306.36 203.12, 357.73 144.26, 399.48 94.19 M255.15 260.22 C310.1 197.2, 364.53 133.07, 399.48 94.19 M260.14 260.58 C306.6 208.36, 353.31 154.47, 399.22 100.59 M260.14 260.58 C314.84 198.11, 370.53 134.35, 399.22 100.59 M265.78 260.19 C310.07 209.18, 353.85 155.07, 399.62 106.23 M265.78 260.19 C313.52 203.42, 361.67 148.41, 399.62 106.23 M270.77 260.55 C312.94 210.71, 350.92 165.03, 399.35 112.63 M270.77 260.55 C313.74 212.09, 354.22 165.25, 399.35 112.63 M276.41 260.15 C322.47 205.94, 367.79 155.3, 399.75 118.27 M276.41 260.15 C319.24 210.97, 362.85 162.61, 399.75 118.27 M281.4 260.51 C309.82 228.04, 337.57 195, 399.49 124.67 M281.4 260.51 C315.37 218.59, 351.47 180.05, 399.49 124.67 M287.04 260.12 C329.88 210.1, 376.19 158.03, 399.23 131.06 M287.04 260.12 C314.45 229.24, 339.01 200.91, 399.23 131.06 M292.03 260.48 C332.66 211.87, 372.61 167.01, 399.62 136.71 M292.03 260.48 C317.01 231.17, 342.37 202.7, 399.62 136.71 M297.67 260.09 C320.79 233.82, 342.65 206.24, 399.36 143.11 M297.67 260.09 C337.01 212.55, 377.78 167.34, 399.36 143.11 M302.66 260.45 C335.5 224.45, 365.99 190.46, 399.75 148.75 M302.66 260.45 C341.03 214.95, 379.75 172.24, 399.75 148.75 M308.3 260.05 C331.34 234.81, 350.27 209.28, 399.49 155.15 M308.3 260.05 C337.96 226.28, 369 190.93, 399.49 155.15 M313.29 260.41 C344.16 226.42, 373.26 191.65, 399.23 161.54 M313.29 260.41 C340.87 229.56, 366.67 199.76, 399.23 161.54 M318.93 260.02 C339.08 235.8, 357.34 215.88, 399.63 167.19 M318.93 260.02 C338.18 238.19, 356.44 217.12, 399.63 167.19 M323.92 260.38 C343.38 240.84, 359.55 216.05, 399.36 173.59 M323.92 260.38 C339.58 242.35, 357.05 223.74, 399.36 173.59 M328.9 260.74 C356.6 230.93, 381.38 201.75, 399.76 179.23 M328.9 260.74 C355.35 229.48, 382.07 199.6, 399.76 179.23 M334.55 260.34 C353.55 235.84, 374.53 215.15, 399.5 185.63 M334.55 260.34 C350.96 240.58, 366.19 222.61, 399.5 185.63 M339.53 260.7 C355.36 243.43, 370.27 221.73, 399.24 192.02 M339.53 260.7 C361.66 234.82, 383.39 210.67, 399.24 192.02 M345.18 260.31 C362.54 238.6, 380.33 220.43, 399.63 197.67 M345.18 260.31 C359.44 243.81, 374.08 228.32, 399.63 197.67 M350.16 260.67 C364.5 243.76, 376.71 230.97, 399.37 204.06 M350.16 260.67 C363.93 245.32, 377.91 229.24, 399.37 204.06 M355.81 260.27 C372.91 242.23, 387.92 223.73, 399.76 209.71 M355.81 260.27 C373.85 240.57, 389.06 220.82, 399.76 209.71 M360.79 260.63 C368.09 252.39, 377.96 242.27, 399.5 216.11 M360.79 260.63 C372.53 247.08, 385.83 232.19, 399.5 216.11 M366.44 260.24 C372.92 252.64, 384.19 241.59, 399.24 222.5 M366.44 260.24 C374.31 251.44, 384.03 240.28, 399.24 222.5 M372.74 259.09 C379.45 251.45, 383.63 244.65, 398.98 228.9 M372.74 259.09 C383.24 247.41, 391.87 236.99, 398.98 228.9 M378.38 258.69 C382.28 255.84, 387.86 247.21, 398.06 236.05 M378.38 258.69 C382.66 254.99, 386.79 248.97, 398.06 236.05 M385.99 256.04 C389.48 251.84, 392.18 249.55, 394.52 246.23 M385.99 256.04 C389.44 252.25, 392.29 247.67, 394.52 246.23" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C123.14 1.36, 217.94 1.27, 366 0 M32 0 C131.29 -1.79, 231.25 -1.6, 366 0 M366 0 C387.8 0.82, 396.14 9, 398 32 M366 0 C388.4 -1.28, 396.3 11.05, 398 32 M398 32 C396.68 77, 397.3 120.08, 398 228 M398 32 C397.91 99.78, 396.94 166.68, 398 228 M398 228 C399.84 247.36, 385.48 260.38, 366 260 M398 228 C399.48 250.74, 389.16 259.77, 366 260 M366 260 C278.41 260.83, 191.33 259.2, 32 260 M366 260 C254.85 261.48, 142.72 261.51, 32 260 M32 260 C9.06 261.46, -1.27 247.64, 0 228 M32 260 C8.96 260.25, 0.79 249.13, 0 228 M0 228 C-2.62 187.23, -0.98 150.02, 0 32 M0 228 C-1.12 169.92, -1.74 111.79, 0 32 M0 32 C-0.23 11.83, 11.85 0.53, 32 0 M0 32 C1.74 9.13, 11.06 1.81, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(284.59193843887 276.159696266393) rotate(0 182.81249999999994 124.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: keep</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <directory-with-keep-digest></text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 1</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> executable: false</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="134.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <empty-blob-digest></text><text x="0" y="153.6" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 0</text><text x="0" y="172.79999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> executable: false</text><text x="0" y="192" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks:</text><text x="0" y="211.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: aa</text><text x="0" y="230.39999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> target: /nix/store/somewhereelse</text></g><g stroke-linecap="round" transform="translate(292.58984375 581.578125) rotate(0 192 36.5)"><path d="M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M3.28 12.13 C5.02 8.82, 8.25 4.62, 11.81 2.32 M3.28 12.13 C6.19 8.3, 9.57 5.6, 11.81 2.32 M1.71 20.04 C8.34 16.13, 10.93 7.61, 18.77 0.41 M1.71 20.04 C6.51 15.46, 9.85 9.62, 18.77 0.41 M2.11 25.68 C10.79 16.53, 14.3 8.85, 23.76 0.77 M2.11 25.68 C8.12 18.52, 15.95 11.63, 23.76 0.77 M1.84 32.08 C8.84 22.24, 16.07 17.29, 29.4 0.38 M1.84 32.08 C11.36 20.47, 23.16 9.07, 29.4 0.38 M2.24 37.72 C9.27 29.77, 15.4 22.85, 34.39 0.74 M2.24 37.72 C15.42 23.1, 27.12 8.74, 34.39 0.74 M2.63 43.36 C8.76 33.58, 17 25.79, 40.03 0.35 M2.63 43.36 C13.14 31.06, 23.17 18.26, 40.03 0.35 M2.37 49.76 C16.36 34.07, 29.4 16.32, 45.02 0.71 M2.37 49.76 C17.93 32.43, 33.93 13.74, 45.02 0.71 M2.77 55.41 C19.05 35.42, 38.7 10.68, 50.66 0.31 M2.77 55.41 C19.96 34.91, 39.43 13.51, 50.66 0.31 M1.19 63.31 C17.21 49, 29.42 31.12, 55.65 0.67 M1.19 63.31 C15.9 46.95, 29.88 30.05, 55.65 0.67 M4.21 65.94 C21.41 44.81, 41.02 26.02, 61.29 0.28 M4.21 65.94 C24.28 42.91, 45.96 20.08, 61.29 0.28 M6.57 69.32 C23.78 51.2, 36.27 33.53, 66.28 0.64 M6.57 69.32 C19.35 53.63, 31.93 38.99, 66.28 0.64 M9.59 71.94 C25.17 56.48, 40.49 38.06, 71.92 0.24 M9.59 71.94 C23.84 58.2, 36.53 43.78, 71.92 0.24 M13.92 73.05 C31.98 52.05, 54.85 25.68, 76.91 0.6 M13.92 73.05 C30.95 53.92, 48.62 33.36, 76.91 0.6 M19.57 72.66 C38.35 51.77, 53.43 34.49, 82.55 0.21 M19.57 72.66 C32.02 56.9, 44.97 41.55, 82.55 0.21 M24.55 73.02 C48.11 46.73, 66.96 24.34, 87.54 0.57 M24.55 73.02 C47.62 46.37, 71.59 19.54, 87.54 0.57 M30.2 72.63 C46.25 51.67, 67.22 31.55, 93.18 0.17 M30.2 72.63 C53.38 46.94, 75.41 21.61, 93.18 0.17 M35.18 72.99 C55.38 48.59, 74.75 27.94, 98.17 0.53 M35.18 72.99 C57.1 47.59, 77.7 22.46, 98.17 0.53 M40.83 72.59 C64.45 43.72, 89.12 19.76, 103.81 0.14 M40.83 72.59 C61.26 47.84, 82.21 23.37, 103.81 0.14 M45.81 72.95 C66.24 52.79, 83.08 30.43, 108.8 0.5 M45.81 72.95 C67.8 48.81, 88.58 24.77, 108.8 0.5 M51.46 72.56 C64.09 57.9, 78.25 44.63, 113.78 0.86 M51.46 72.56 C72.83 47.42, 94.74 22.72, 113.78 0.86 M56.44 72.92 C74.65 52.58, 96.04 31.05, 119.43 0.46 M56.44 72.92 C81.73 45.16, 105.23 18.3, 119.43 0.46 M62.09 72.52 C80.07 51.93, 98.45 29.82, 124.41 0.82 M62.09 72.52 C79.04 54.05, 95.96 34.96, 124.41 0.82 M67.07 72.88 C80.12 59.08, 92.94 42.21, 130.06 0.43 M67.07 72.88 C80.66 58.14, 95.45 40.91, 130.06 0.43 M72.06 73.24 C93.79 49.66, 111.38 26.32, 135.04 0.79 M72.06 73.24 C89.58 51.72, 107.82 32.09, 135.04 0.79 M77.7 72.85 C104.72 44.35, 127.17 17, 140.69 0.4 M77.7 72.85 C96.13 51.38, 114.98 29.95, 140.69 0.4 M82.69 73.21 C104.71 47.57, 127.59 22.31, 145.67 0.76 M82.69 73.21 C101.71 51.47, 120.77 29.01, 145.67 0.76 M88.33 72.81 C110.76 47.24, 131.64 20.5, 151.32 0.36 M88.33 72.81 C109 48.14, 130.72 23.09, 151.32 0.36 M93.32 73.17 C111.15 55.01, 126.29 31.78, 156.3 0.72 M93.32 73.17 C110.49 51.39, 129.9 29.91, 156.3 0.72 M98.96 72.78 C118.26 54.28, 133.1 34.79, 161.95 0.33 M98.96 72.78 C111.06 59.03, 124.81 44.07, 161.95 0.33 M103.95 73.14 C115.72 58.92, 129.64 40.85, 166.93 0.69 M103.95 73.14 C126.22 47.2, 149.58 21.56, 166.93 0.69 M109.59 72.74 C123.31 56.85, 140.06 38.01, 172.58 0.29 M109.59 72.74 C124.38 56.56, 137.2 40.37, 172.58 0.29 M114.58 73.1 C138.07 45.66, 163.14 15.75, 177.56 0.65 M114.58 73.1 C131.86 52.94, 147.99 32.77, 177.56 0.65 M120.22 72.71 C133.34 57.46, 150.05 38.7, 183.21 0.26 M120.22 72.71 C135.3 57.12, 148.59 39.51, 183.21 0.26 M125.21 73.07 C147.35 45.64, 169.73 21.54, 188.19 0.62 M125.21 73.07 C146.83 48.56, 167.28 24.07, 188.19 0.62 M130.85 72.68 C149.9 47.27, 173.04 22.78, 193.84 0.22 M130.85 72.68 C153.31 46.45, 175.71 22.68, 193.84 0.22 M135.84 73.04 C157.75 47.97, 181.46 23.22, 198.82 0.58 M135.84 73.04 C157.01 47.37, 178.52 21.52, 198.82 0.58 M141.49 72.64 C163.6 47.54, 184.92 24.28, 203.81 0.94 M141.49 72.64 C157.97 55.11, 171.75 36.25, 203.81 0.94 M146.47 73 C164.74 50.07, 184.75 31.86, 209.45 0.55 M146.47 73 C169.77 45.07, 195.2 17.9, 209.45 0.55 M152.12 72.61 C170.98 50.38, 191.11 25.57, 214.44 0.91 M152.12 72.61 C167.16 54.48, 184.74 36.2, 214.44 0.91 M157.1 72.97 C171.7 55.53, 188.24 37.45, 220.08 0.51 M157.1 72.97 C171.53 57.7, 184.96 42.02, 220.08 0.51 M162.09 73.33 C182.79 52.34, 200.46 29.24, 225.07 0.87 M162.09 73.33 C176.54 57.94, 188.74 43.02, 225.07 0.87 M167.73 72.93 C187.78 52.57, 204.67 31.34, 230.71 0.48 M167.73 72.93 C192.31 45.48, 215.11 18.16, 230.71 0.48 M172.72 73.29 C192.03 53.03, 213.93 26.16, 235.7 0.84 M172.72 73.29 C187.14 54.62, 203.69 37.67, 235.7 0.84 M178.36 72.9 C196.89 49.98, 218.6 27.24, 241.34 0.45 M178.36 72.9 C191.33 57.21, 204.9 42.89, 241.34 0.45 M183.35 73.26 C207.11 47.48, 230.29 17.13, 246.33 0.81 M183.35 73.26 C195.69 56.95, 208.85 41.55, 246.33 0.81 M188.99 72.86 C203.94 55.74, 221.89 34.74, 251.97 0.41 M188.99 72.86 C203.18 55.08, 218.22 38.46, 251.97 0.41 M193.98 73.22 C219.55 44.07, 240.87 19.79, 256.96 0.77 M193.98 73.22 C208.25 55.94, 223.54 39.09, 256.96 0.77 M199.62 72.83 C217.99 48.42, 241.49 28.33, 262.6 0.38 M199.62 72.83 C214.61 56.41, 229.03 40.98, 262.6 0.38 M204.61 73.19 C223.33 53.31, 241.74 30.68, 267.59 0.74 M204.61 73.19 C229.17 46.22, 251.07 19.59, 267.59 0.74 M210.25 72.79 C224.49 54.46, 241.18 34.58, 273.23 0.34 M210.25 72.79 C227.21 52.37, 245.03 32.68, 273.23 0.34 M215.24 73.15 C229.43 57.1, 240.31 42.47, 278.22 0.7 M215.24 73.15 C237.32 48.52, 258.37 22.2, 278.22 0.7 M220.88 72.76 C240.5 49.7, 259.92 29.24, 283.86 0.31 M220.88 72.76 C245.23 46.31, 268.29 19.17, 283.86 0.31 M225.87 73.12 C244.42 48.72, 265.88 27.95, 288.85 0.67 M225.87 73.12 C251.66 44.25, 275.97 16.49, 288.85 0.67 M231.51 72.72 C253.24 45.13, 279.63 17.21, 293.84 1.03 M231.51 72.72 C244.39 57.29, 257.3 41.73, 293.84 1.03 M236.5 73.08 C256.68 52.52, 274.01 31.26, 299.48 0.63 M236.5 73.08 C252.49 52.88, 270.22 34.34, 299.48 0.63 M242.14 72.69 C264.56 48.95, 287.34 23.02, 304.47 0.99 M242.14 72.69 C265.99 45.72, 289.13 17.36, 304.47 0.99 M247.13 73.05 C268.76 49.79, 290 25.19, 310.11 0.6 M247.13 73.05 C271.93 45.31, 297.4 16.35, 310.11 0.6 M252.77 72.66 C272.81 49.72, 292.69 22.65, 315.1 0.96 M252.77 72.66 C274.82 46.14, 296.99 21.06, 315.1 0.96 M257.76 73.02 C279.06 47.65, 296.04 26.13, 320.74 0.56 M257.76 73.02 C279.13 48.78, 298.14 26.64, 320.74 0.56 M262.75 73.38 C286.03 44.74, 309.2 19.59, 325.73 0.92 M262.75 73.38 C284.29 48.03, 306.79 23.96, 325.73 0.92 M268.39 72.98 C284.12 55.13, 298.79 37.14, 331.37 0.53 M268.39 72.98 C286.16 50.17, 306.05 30.27, 331.37 0.53 M273.38 73.34 C296.56 45.22, 323.75 15.16, 336.36 0.89 M273.38 73.34 C289.13 55.59, 302.31 40.33, 336.36 0.89 M279.02 72.95 C303.53 43.87, 326.85 18.13, 342 0.49 M279.02 72.95 C293.37 55.49, 308.15 39.18, 342 0.49 M284.01 73.31 C298.42 57.71, 311.23 40.55, 346.99 0.85 M284.01 73.31 C308.02 43.63, 333.49 16.03, 346.99 0.85 M289.65 72.91 C311.51 49.12, 331.37 27.36, 352.63 0.46 M289.65 72.91 C314.28 43.1, 339.52 15.91, 352.63 0.46 M294.64 73.27 C311.15 55.94, 323.35 38.14, 357.62 0.82 M294.64 73.27 C315.12 50.08, 336.79 25.51, 357.62 0.82 M300.28 72.88 C323.32 47.78, 344.6 22, 363.26 0.43 M300.28 72.88 C320.59 50.02, 339.32 28.35, 363.26 0.43 M305.27 73.24 C321.19 53.97, 335.23 38.9, 368.25 0.79 M305.27 73.24 C320.38 55.99, 334.56 39.62, 368.25 0.79 M310.91 72.84 C326.89 57.23, 339.69 36.32, 372.58 1.9 M310.91 72.84 C323.53 58.18, 338.06 42.96, 372.58 1.9 M315.9 73.2 C339.86 47.6, 361.01 22.6, 376.91 3.02 M315.9 73.2 C338.69 46.17, 361.68 20.58, 376.91 3.02 M321.54 72.81 C338.51 50.61, 357.46 32.27, 379.93 5.64 M321.54 72.81 C336.35 54.99, 349.93 38.93, 379.93 5.64 M326.53 73.17 C341.29 57.14, 355.14 36.64, 382.29 9.02 M326.53 73.17 C347.2 48.97, 367.48 26.48, 382.29 9.02 M332.17 72.77 C348.91 51.78, 366.05 34.35, 384.66 12.4 M332.17 72.77 C345.89 56.87, 360.02 41.97, 384.66 12.4 M337.16 73.13 C350.96 56.83, 362.64 44.64, 384.39 18.8 M337.16 73.13 C350.38 58.41, 363.8 42.97, 384.39 18.8 M342.8 72.74 C358.9 55.85, 372.92 38.47, 384.13 25.19 M342.8 72.74 C359.81 54.2, 374.02 35.6, 384.13 25.19 M347.79 73.1 C354.66 65.33, 364.11 55.68, 384.53 30.84 M347.79 73.1 C358.88 60.26, 371.55 46.08, 384.53 30.84 M352.77 73.46 C358.94 66.21, 369.89 55.52, 384.27 37.23 M352.77 73.46 C360.31 65.04, 369.69 54.26, 384.27 37.23 M358.42 73.07 C364.98 65.59, 369.02 58.96, 384 43.63 M358.42 73.07 C368.68 61.66, 377.08 51.51, 384 43.63 M363.4 73.43 C367.59 70.26, 373.45 61.3, 384.4 49.27 M363.4 73.43 C367.97 69.41, 372.38 63.07, 384.4 49.27 M367.74 74.54 C374.28 66.78, 379.58 61.94, 384.14 55.67 M367.74 74.54 C374.31 67.19, 380 58.66, 384.14 55.67 M374.69 72.64 C376.19 69.99, 382.01 66.54, 385.19 60.56 M374.69 72.64 C377.37 68.95, 380.91 65.58, 385.19 60.56" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M18.25 0 M18.25 0 C129.54 2.72, 242.28 1.14, 365.75 0 M18.25 0 C127.66 -1.41, 238.58 -1.21, 365.75 0 M365.75 0 C378.27 0.9, 382.95 5.36, 384 18.25 M365.75 0 C380.07 0.47, 383.93 4.97, 384 18.25 M384 18.25 C384.02 33.84, 384.87 48.45, 384 54.75 M384 18.25 C384.01 28.7, 384.22 39.53, 384 54.75 M384 54.75 C382.32 67.61, 378.7 73.98, 365.75 73 M384 54.75 C385.93 68.19, 378.42 74.36, 365.75 73 M365.75 73 C288.24 70.59, 213.1 72.01, 18.25 73 M365.75 73 C265.08 74.84, 163.23 75.38, 18.25 73 M18.25 73 C7.8 72.9, 1.58 68.54, 0 54.75 M18.25 73 C7.06 72.31, -0.46 65.42, 0 54.75 M0 54.75 C0.31 45.73, -1.55 34.03, 0 18.25 M0 54.75 C0.72 40.09, -0.05 27.49, 0 18.25 M0 18.25 C1.55 7.28, 6.06 -1.74, 18.25 0 M0 18.25 C1.91 4.6, 5.29 1.6, 18.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(297.58984375 586.578125) rotate(0 70.3125 28.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(297.01171875 700.19140625) rotate(0 192 63)"><path d="M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M1.14 22.19 C5.68 16.12, 10.34 11.35, 18.19 2.57 M1.14 22.19 C5.63 15.12, 11.63 10.54, 18.19 2.57 M2.84 26.32 C9.4 19.34, 14.38 14.47, 24.49 1.42 M2.84 26.32 C7.02 21.88, 12.43 16.24, 24.49 1.42 M3.24 31.97 C12.37 22.32, 23.31 9.97, 30.14 1.02 M3.24 31.97 C8.75 26.69, 14.07 19.43, 30.14 1.02 M2.98 38.36 C11.61 29.09, 22.34 16.73, 34.47 2.14 M2.98 38.36 C14.61 26.7, 24.62 13.57, 34.47 2.14 M2.72 44.76 C8.44 35.48, 20.15 28.58, 39.46 2.5 M2.72 44.76 C9.81 35.38, 16.68 27.81, 39.46 2.5 M3.11 50.4 C17.54 33.89, 29.94 20.31, 45.1 2.1 M3.11 50.4 C20.74 31.63, 36.06 13.37, 45.1 2.1 M2.85 56.8 C18.58 35.97, 39.38 14.61, 50.09 2.46 M2.85 56.8 C15.02 42.55, 25.62 29.03, 50.09 2.46 M3.24 62.45 C13.28 49.17, 26.13 38.7, 55.73 2.07 M3.24 62.45 C14.41 50.21, 23.72 38.66, 55.73 2.07 M2.98 68.84 C13.36 56.63, 24.49 40.18, 60.72 2.43 M2.98 68.84 C25.38 43.36, 46.78 18.82, 60.72 2.43 M2.72 75.24 C27.68 45.78, 49.87 21.46, 66.36 2.03 M2.72 75.24 C27.36 48.58, 50.99 21.41, 66.36 2.03 M3.12 80.88 C26.98 53.98, 51.79 26.74, 71.35 2.39 M3.12 80.88 C24.82 57.11, 46.21 30.7, 71.35 2.39 M2.85 87.28 C25.83 58.82, 49.6 32.81, 76.33 2.75 M2.85 87.28 C18.86 69.84, 32.85 53.27, 76.33 2.75 M3.25 92.92 C31.02 64.11, 55.21 32.05, 81.98 2.36 M3.25 92.92 C29.09 64.51, 54.01 35.08, 81.98 2.36 M0.36 102.34 C18.06 82.88, 34.35 63.19, 86.96 2.72 M0.36 102.34 C22.5 76.81, 45.99 50.02, 86.96 2.72 M2.07 106.47 C35.94 64.04, 71.16 24.59, 92.61 2.33 M2.07 106.47 C29.3 74.98, 57.21 42, 92.61 2.33 M3.78 110.61 C32.64 72.16, 65.08 38.52, 97.59 2.69 M3.78 110.61 C27.71 83.59, 50.59 56.73, 97.59 2.69 M5.48 114.74 C31.81 84.38, 61.24 51.3, 103.24 2.29 M5.48 114.74 C38.37 76.78, 69.65 40.61, 103.24 2.29 M7.85 118.12 C37.24 84.78, 67.86 51.75, 108.22 2.65 M7.85 118.12 C40.51 79.72, 74.23 40.65, 108.22 2.65 M10.86 120.75 C33.15 93.28, 59.27 65.67, 113.87 2.26 M10.86 120.75 C41.44 83.49, 74.67 47.06, 113.87 2.26 M14.54 122.62 C54.9 77.33, 93.45 31.72, 118.85 2.62 M14.54 122.62 C36.96 96.13, 58.59 69.9, 118.85 2.62 M18.21 124.48 C60.03 78.24, 98.36 27.73, 124.5 2.22 M18.21 124.48 C47.99 89.82, 78.58 52.73, 124.5 2.22 M23.2 124.84 C60.93 81.34, 96.9 37.41, 129.48 2.58 M23.2 124.84 C51.15 90.76, 80.7 57.55, 129.48 2.58 M27.53 125.96 C67.64 79.48, 108.95 32.16, 135.13 2.19 M27.53 125.96 C50.35 100, 70.45 76.16, 135.13 2.19 M32.52 126.32 C67.96 82.8, 105.29 42.95, 140.11 2.55 M32.52 126.32 C55.09 102.84, 75.65 76.61, 140.11 2.55 M37.51 126.68 C76.58 83.49, 111.39 39.46, 145.76 2.15 M37.51 126.68 C68.68 90.35, 99.78 54.4, 145.76 2.15 M43.15 126.29 C79.05 86.81, 113.32 46.01, 150.74 2.51 M43.15 126.29 C83.16 78.74, 122.47 32.63, 150.74 2.51 M48.14 126.65 C84.95 86.46, 121.01 43.96, 156.39 2.12 M48.14 126.65 C77.47 92.07, 106.15 57.86, 156.39 2.12 M53.12 127.01 C94.06 83.46, 132.13 35.83, 161.37 2.48 M53.12 127.01 C83.35 90.25, 115.88 54.9, 161.37 2.48 M58.77 126.61 C88.9 91.94, 120.62 55.5, 167.02 2.08 M58.77 126.61 C80.71 101.71, 103.08 74.94, 167.02 2.08 M63.75 126.97 C94.83 94.26, 121.35 59.65, 172 2.44 M63.75 126.97 C99.19 87.16, 134.08 46.33, 172 2.44 M69.4 126.58 C96.06 98.29, 123.08 66.58, 177.65 2.05 M69.4 126.58 C94.05 98.86, 118.43 72.82, 177.65 2.05 M74.38 126.94 C110.22 87.09, 146.32 44.12, 182.63 2.41 M74.38 126.94 C112.7 80.36, 152.52 34.24, 182.63 2.41 M80.03 126.54 C112.63 85.84, 146.64 47.21, 188.28 2.01 M80.03 126.54 C118.52 83.47, 154.62 39.47, 188.28 2.01 M85.01 126.9 C123.66 84.49, 160.64 41.03, 193.26 2.37 M85.01 126.9 C107.24 101.3, 128.84 75.24, 193.26 2.37 M90.66 126.51 C128.32 79.98, 168.35 34.11, 198.25 2.73 M90.66 126.51 C125.23 87.51, 159.47 48.47, 198.25 2.73 M95.64 126.87 C136.32 80.89, 176.13 32.73, 203.89 2.34 M95.64 126.87 C130.67 85.7, 167.03 45.03, 203.89 2.34 M101.29 126.47 C138.72 85.68, 174.7 42.24, 208.88 2.7 M101.29 126.47 C141.27 81.26, 181.48 32.8, 208.88 2.7 M106.27 126.83 C133.13 96.99, 159.87 69.21, 214.52 2.31 M106.27 126.83 C139.22 87.92, 173.99 50.04, 214.52 2.31 M111.92 126.44 C139.17 92.95, 170.03 64.03, 219.51 2.67 M111.92 126.44 C140.35 94.34, 169.24 63.52, 219.51 2.67 M116.9 126.8 C139.41 102.01, 161.49 74.65, 225.15 2.27 M116.9 126.8 C143.96 97.47, 169.6 65.58, 225.15 2.27 M122.55 126.4 C151.81 92.7, 184.46 60.16, 230.14 2.63 M122.55 126.4 C144.55 101.98, 167.71 77.15, 230.14 2.63 M127.53 126.76 C155.24 93.9, 184.87 59.39, 235.78 2.24 M127.53 126.76 C162.55 85.69, 197.14 44.32, 235.78 2.24 M133.18 126.37 C156.24 97.59, 181.25 74.22, 240.77 2.6 M133.18 126.37 C169.91 83.87, 205.04 43.06, 240.77 2.6 M138.16 126.73 C171.62 83.67, 210.01 43.26, 246.41 2.2 M138.16 126.73 C162.75 98.55, 185.88 71.86, 246.41 2.2 M143.81 126.33 C167.53 96.38, 195.44 68.77, 251.4 2.56 M143.81 126.33 C175.38 87.65, 208.52 52.45, 251.4 2.56 M148.79 126.69 C186.15 81.29, 226.32 36.57, 257.04 2.17 M148.79 126.69 C185.38 86.13, 221.92 44.72, 257.04 2.17 M154.44 126.3 C190.78 83.15, 229.34 36.03, 262.03 2.53 M154.44 126.3 C178.51 97.01, 203.83 70.69, 262.03 2.53 M159.42 126.66 C189.83 93.77, 217.8 63.16, 267.67 2.13 M159.42 126.66 C196.22 84.11, 234.53 42.17, 267.67 2.13 M165.07 126.27 C199.58 88.17, 233.46 45.58, 272.66 2.49 M165.07 126.27 C204.81 83.18, 243.36 38.31, 272.66 2.49 M170.05 126.63 C191.4 102.17, 214.65 75.55, 278.3 2.1 M170.05 126.63 C194.79 98.87, 219.21 68.85, 278.3 2.1 M175.04 126.99 C218.62 76.89, 259.45 31.99, 283.29 2.46 M175.04 126.99 C200.78 100.7, 225.33 71.6, 283.29 2.46 M180.68 126.59 C214.02 88.6, 246.67 51.74, 288.93 2.06 M180.68 126.59 C212.29 91.19, 241.62 56.43, 288.93 2.06 M185.67 126.95 C215.71 95.07, 243.49 60.99, 293.92 2.42 M185.67 126.95 C216.01 94.84, 243.45 62.69, 293.92 2.42 M191.31 126.56 C229.63 85.76, 263.97 41.71, 299.56 2.03 M191.31 126.56 C222.59 88.19, 255.96 51.19, 299.56 2.03 M196.3 126.92 C217.19 100.03, 243 74.87, 304.55 2.39 M196.3 126.92 C218.88 99.48, 243.28 72.58, 304.55 2.39 M201.94 126.52 C245.64 78.21, 284.33 30.24, 309.54 2.75 M201.94 126.52 C240.53 84.15, 277.64 41.48, 309.54 2.75 M206.93 126.88 C248.75 80.42, 287.71 35.97, 315.18 2.36 M206.93 126.88 C240.59 87.82, 273.05 49.59, 315.18 2.36 M212.57 126.49 C242.06 93.98, 270.55 61.9, 320.17 2.72 M212.57 126.49 C238.53 99.42, 261.94 72.17, 320.17 2.72 M217.56 126.85 C255.21 80.1, 296.4 36.1, 325.81 2.32 M217.56 126.85 C248.46 91.27, 280.97 56.5, 325.81 2.32 M223.2 126.45 C245.32 96.89, 272.92 68.02, 330.8 2.68 M223.2 126.45 C246.93 99.22, 269.26 73.84, 330.8 2.68 M228.19 126.81 C266.27 81.56, 302.8 39.44, 336.44 2.29 M228.19 126.81 C268.68 80.87, 310.31 31.49, 336.44 2.29 M233.83 126.42 C261.99 92.9, 293.86 58.89, 341.43 2.65 M233.83 126.42 C265.72 90.09, 297.62 53.24, 341.43 2.65 M238.82 126.78 C276.56 81.01, 314.76 40.31, 347.07 2.25 M238.82 126.78 C275.07 83.85, 309.78 43.16, 347.07 2.25 M244.47 126.38 C281.08 86.31, 311.83 47.08, 353.37 1.1 M244.47 126.38 C284.42 79.98, 324.2 34.41, 353.37 1.1 M249.45 126.74 C283.83 89.61, 316.66 48.25, 358.36 1.46 M249.45 126.74 C288.06 81.49, 329.09 35.62, 358.36 1.46 M255.1 126.35 C286.95 88.5, 321.78 49.74, 362.69 2.58 M255.1 126.35 C278.54 101.51, 299.57 76.6, 362.69 2.58 M260.08 126.71 C289.9 92.16, 318.76 59.09, 367.68 2.94 M260.08 126.71 C286.6 97.68, 309.24 69.16, 367.68 2.94 M265.73 126.32 C304.67 81.34, 343.89 36.13, 371.35 4.81 M265.73 126.32 C296.05 89.95, 328.32 54.46, 371.35 4.81 M270.71 126.68 C312.11 79.85, 350.1 38.08, 375.03 6.68 M270.71 126.68 C308.29 85.27, 344.32 43.1, 375.03 6.68 M276.36 126.28 C302.37 95.05, 332.55 61.6, 377.39 10.06 M276.36 126.28 C300.17 99.46, 322.39 72.19, 377.39 10.06 M281.34 126.64 C304.76 96.82, 332.3 67.75, 380.41 12.68 M281.34 126.64 C307.49 95.18, 332.91 65.5, 380.41 12.68 M286.33 127 C314.42 93.01, 345.57 57.56, 381.46 17.57 M286.33 127 C313.51 94.85, 341.77 62.68, 381.46 17.57 M291.97 126.61 C313.48 100.45, 339.17 75.97, 383.16 21.7 M291.97 126.61 C311.58 103.09, 333.11 79.56, 383.16 21.7 M296.96 126.97 C320.59 100.34, 342.62 72.36, 384.87 25.84 M296.96 126.97 C321.66 99.71, 344.27 72.56, 384.87 25.84 M302.6 126.57 C334.11 86.35, 368.78 51.97, 385.27 31.48 M302.6 126.57 C330.75 94.29, 357.85 62.13, 385.27 31.48 M307.59 126.93 C326.81 105.47, 343.03 83.78, 385 37.88 M307.59 126.93 C327.54 105.51, 344.6 84.21, 385 37.88 M313.23 126.54 C332.05 106.16, 349.05 86.64, 385.4 43.52 M313.23 126.54 C332.89 103.96, 352.85 83.32, 385.4 43.52 M318.22 126.9 C331.98 110.58, 346.57 93.14, 385.14 49.92 M318.22 126.9 C331.03 111.1, 344.19 94.95, 385.14 49.92 M323.86 126.5 C335.72 108.89, 350.56 93.22, 385.53 55.56 M323.86 126.5 C345.11 101.47, 364.04 79.57, 385.53 55.56 M328.85 126.86 C346.1 108.02, 361.21 90.1, 385.27 61.96 M328.85 126.86 C342.2 112.06, 355.94 96.74, 385.27 61.96 M334.49 126.47 C350.84 109.97, 364.71 94.6, 385.67 67.6 M334.49 126.47 C353.49 104.81, 372.17 82.14, 385.67 67.6 M339.48 126.83 C348.96 114.71, 363.17 101.01, 385.4 74 M339.48 126.83 C354.75 110.09, 368.15 93.09, 385.4 74 M345.12 126.43 C354.84 117.22, 365.07 103.77, 385.8 79.64 M345.12 126.43 C358.46 110.23, 371.31 95.91, 385.8 79.64 M350.11 126.79 C364.3 111.08, 375.22 97.18, 385.54 86.04 M350.11 126.79 C363.14 112.28, 374.94 98.31, 385.54 86.04 M354.44 127.91 C362.81 117.31, 370.87 110.18, 385.28 92.44 M354.44 127.91 C362.9 118.85, 369.83 111.91, 385.28 92.44 M360.74 126.76 C367.63 119.85, 374.61 113.88, 386.98 96.57 M360.74 126.76 C369 116.72, 377.31 108.85, 386.98 96.57 M367.7 124.86 C371.04 118.25, 376.15 112.61, 382.13 108.25 M367.7 124.86 C371.11 120.48, 374.22 117.39, 382.13 108.25" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M31.5 0 M31.5 0 C144.73 2.16, 257.89 1.55, 352.5 0 M31.5 0 C123.21 -0.03, 215.81 -0.04, 352.5 0 M352.5 0 C373.48 -1.54, 384.54 11.54, 384 31.5 M352.5 0 C371.52 1.89, 382.06 11.18, 384 31.5 M384 31.5 C384.2 57.65, 382.83 81.45, 384 94.5 M384 31.5 C384.94 50.55, 383.94 67.7, 384 94.5 M384 94.5 C385.07 116.86, 374.35 127.03, 352.5 126 M384 94.5 C385.17 117.04, 373.57 125.11, 352.5 126 M352.5 126 C247.11 128.47, 140.42 127.09, 31.5 126 M352.5 126 C271.97 126.76, 193.29 127.03, 31.5 126 M31.5 126 C11.34 124.76, -1.26 114.07, 0 94.5 M31.5 126 C9.77 126.57, -1.64 113.31, 0 94.5 M0 94.5 C0.36 74.76, 1.16 53.5, 0 31.5 M0 94.5 C-0.19 81.51, -0.09 68.38, 0 31.5 M0 31.5 C-0.91 8.69, 10.9 0.79, 31.5 0 M0 31.5 C2.16 12.64, 11.39 -0.47, 31.5 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(302.01171875 705.19140625) rotate(0 145.3125 57.60000000000002)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: a</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <directory-a-digest></text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 0</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g transform="translate(303.75390625 45.62890625) rotate(0 89.0625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_WITH_KEEP</text></g><g transform="translate(308.25390625 240.4765625) rotate(0 98.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_COMPLICATED</text></g><g transform="translate(307.28125 554.6148437500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_A</text></g><g transform="translate(310.6875 674.4585937500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_B</text></g><g transform="translate(18.95703125 42.53671874999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_A</text></g><g transform="translate(22.55078125 130.24374999999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_B</text></g><g transform="translate(13.5546875 210.27109374999998) rotate(0 46.875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_EMPTY</text></g><g stroke-linecap="round"><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-0.52 1.13 C-60.08 19.9, -297.46 94.83, -357.2 113.61 M1.4 0.68 C-58.25 18.98, -298.08 93.09, -357.74 111.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-331.96 94.73 C-340.78 97.36, -347.89 102.93, -357.37 112.7 M-333.95 93.43 C-339.75 97.47, -345.42 102.99, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-325.89 114.33 C-336.76 110.86, -345.74 110.35, -357.37 112.7 M-327.89 113.04 C-335.08 112.25, -342.26 112.89, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M-0.42 1.04 C43.76 -38.95, 220.7 -199.19, 264.79 -239.05 M1.55 0.54 C45.63 -39.42, 220.36 -198.5, 263.94 -238.07" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M251.72 -211.19 C251.81 -219.53, 257.18 -225.36, 265.07 -236.65 M250.73 -212.1 C253.06 -218.02, 257.3 -223.31, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M237.91 -226.37 C241.88 -230.52, 251.02 -232.22, 265.07 -236.65 M236.92 -227.29 C242.47 -229.5, 250.03 -231.14, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M0.21 0.21 C-60.17 -24.98, -301.37 -126.03, -361.5 -151.72 M-1.14 -0.73 C-61.8 -26.18, -302.73 -127.84, -362.74 -153.25" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-332.43 -152.64 C-341.83 -151.66, -356.48 -151.02, -362.88 -151.77 M-332.23 -151.12 C-344.25 -151.99, -356 -152.45, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-340.42 -133.74 C-346.81 -139.68, -358.54 -145.95, -362.88 -151.77 M-340.22 -132.22 C-349.22 -140.26, -357.91 -147.96, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M-0.84 0.01 C13.92 -16, 74.3 -80.87, 89.45 -97.18 M0.92 -1.04 C15.95 -16.73, 76.9 -79.62, 91.58 -95.58" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M79.65 -69.91 C84.59 -79.16, 87.99 -87.88, 90.6 -97.23 M78.91 -67.92 C83.62 -76.08, 87.25 -85.83, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M64.75 -84.03 C75.18 -87.85, 84.2 -91.25, 90.6 -97.23 M64.01 -82.04 C73.25 -85.89, 81.47 -91.29, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(796.08984375 10.884374999999977) rotate(0 37.5 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">PathInfo</text></g><g stroke-linecap="round" transform="translate(800.94921875 70.3515625) rotate(0 238.5 66.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C10.39 14.59, 12.36 7.63, 18.1 2.48 M3.66 19.09 C7.72 12.99, 12.69 8.45, 18.1 2.48 M2.75 26.24 C9.54 18.05, 20.15 8.22, 24.4 1.33 M2.75 26.24 C10.63 16.6, 19.07 7.71, 24.4 1.33 M2.48 32.64 C10.84 25.1, 18.62 16.99, 30.7 0.18 M2.48 32.64 C10.3 22, 19.4 12.2, 30.7 0.18 M2.88 38.28 C13.73 28.82, 21.58 17.53, 34.37 2.05 M2.88 38.28 C11.28 29.81, 18.49 19.2, 34.37 2.05 M2.62 44.68 C15.27 32.46, 26.63 18.87, 39.36 2.41 M2.62 44.68 C12.58 32.64, 23.98 18.88, 39.36 2.41 M2.36 51.07 C15.78 39.12, 25.44 23.33, 45 2.02 M2.36 51.07 C15.86 35.07, 30.03 18.81, 45 2.02 M2.75 56.72 C21.32 37.62, 39.51 16.97, 49.99 2.38 M2.75 56.72 C15.6 42.48, 27.62 27.63, 49.99 2.38 M2.49 63.11 C19.73 43.61, 35.49 24.47, 55.63 1.98 M2.49 63.11 C14.04 50.89, 25.8 36.73, 55.63 1.98 M2.88 68.76 C20.08 47.93, 38.2 25.58, 60.62 2.34 M2.88 68.76 C19.66 48.91, 37.22 28.6, 60.62 2.34 M2.62 75.16 C19.27 53.2, 38.94 35.17, 66.26 1.95 M2.62 75.16 C17.89 55.5, 34.42 36.25, 66.26 1.95 M2.36 81.55 C30.5 51, 58.03 17.88, 71.25 2.31 M2.36 81.55 C16.96 64.32, 31.61 47.37, 71.25 2.31 M2.76 87.2 C31.87 55.75, 56.06 22.7, 76.89 1.91 M2.76 87.2 C24.31 61.65, 46.29 35.63, 76.89 1.91 M2.49 93.59 C32.62 61.39, 61.22 28.82, 81.88 2.27 M2.49 93.59 C23.69 67.97, 45.72 43.7, 81.88 2.27 M2.89 99.24 C23.68 73.49, 45.29 48.44, 87.52 1.88 M2.89 99.24 C26.99 71.79, 50.36 44.89, 87.52 1.88 M2.63 105.64 C23.82 79.99, 47.73 56.76, 92.51 2.24 M2.63 105.64 C29.41 76.09, 53.71 47.33, 92.51 2.24 M3.02 111.28 C35.76 74.46, 69.37 34.13, 98.15 1.85 M3.02 111.28 C24.1 86.82, 46.96 62.48, 98.15 1.85 M4.73 115.41 C41.64 74.79, 76.39 31.84, 103.14 2.21 M4.73 115.41 C35.15 80.63, 65.61 44.22, 103.14 2.21 M5.12 121.06 C38.67 80.99, 74.17 42.93, 108.78 1.81 M5.12 121.06 C37.52 87.2, 67.81 51.07, 108.78 1.81 M8.14 123.68 C45.61 75.63, 85.4 30.08, 113.77 2.17 M8.14 123.68 C46.84 78.16, 85.53 34.79, 113.77 2.17 M10.51 127.06 C51.38 83, 90.78 34.09, 119.41 1.78 M10.51 127.06 C44.83 86.58, 79.34 47.77, 119.41 1.78 M13.52 129.68 C38.07 99.79, 58.32 73.05, 124.4 2.14 M13.52 129.68 C50.9 87.61, 88.06 45.68, 124.4 2.14 M17.2 131.55 C58.64 84.2, 95.67 41.29, 130.04 1.74 M17.2 131.55 C41.93 103.22, 66.7 72.66, 130.04 1.74 M20.87 133.42 C57.62 92.2, 92.65 51.53, 135.03 2.1 M20.87 133.42 C65.94 80.91, 110.67 28.91, 135.03 2.1 M25.86 133.78 C57.72 93.6, 90.77 57.22, 140.67 1.71 M25.86 133.78 C56.65 99.16, 86.75 62.82, 140.67 1.71 M30.19 134.9 C76.39 84.32, 123.04 30.78, 145.66 2.07 M30.19 134.9 C59.94 102.4, 88.51 68.91, 145.66 2.07 M35.18 135.26 C63.65 103.83, 88.19 71.92, 150.65 2.43 M35.18 135.26 C77.05 84.73, 121.83 35.2, 150.65 2.43 M40.17 135.62 C76.62 93.15, 111.75 54.6, 156.29 2.03 M40.17 135.62 C72.44 97.03, 106.71 60.31, 156.29 2.03 M45.81 135.22 C68.12 108.89, 93.36 81.85, 161.28 2.39 M45.81 135.22 C93.12 81.98, 137.9 28.67, 161.28 2.39 M50.8 135.58 C99.08 85.21, 141.4 29.85, 166.92 2 M50.8 135.58 C95.54 85.04, 137.27 35.33, 166.92 2 M56.44 135.19 C86.16 100.81, 118.44 64.39, 171.91 2.36 M56.44 135.19 C85.59 100.01, 117.34 65.71, 171.91 2.36 M61.43 135.55 C93.02 99.32, 126.23 60.67, 177.55 1.96 M61.43 135.55 C98.63 92.46, 135.75 49.47, 177.55 1.96 M66.41 135.91 C88.8 108.25, 112.86 81.48, 182.54 2.32 M66.41 135.91 C91.41 106.52, 117.33 75.61, 182.54 2.32 M72.06 135.51 C104.73 95.48, 142.51 55.2, 188.18 1.93 M72.06 135.51 C99.45 104.83, 124.74 75.89, 188.18 1.93 M77.04 135.87 C115.11 94.08, 147.51 55.24, 193.17 2.29 M77.04 135.87 C100.11 110.76, 122.58 83.57, 193.17 2.29 M82.69 135.48 C116.4 97.33, 152.41 55.98, 198.81 1.9 M82.69 135.48 C110.99 102.8, 139.68 69.49, 198.81 1.9 M87.67 135.84 C132 84.75, 175.61 37.52, 203.8 2.26 M87.67 135.84 C116.95 102.95, 145.57 69.87, 203.8 2.26 M93.32 135.44 C137.75 81.91, 184.41 30.9, 209.44 1.86 M93.32 135.44 C125.76 94, 160.68 54.19, 209.44 1.86 M98.3 135.8 C129.81 99.97, 161.28 60.26, 214.43 2.22 M98.3 135.8 C133.08 96.91, 166.85 57.98, 214.43 2.22 M103.95 135.41 C130.43 101.26, 160.91 69.64, 220.07 1.83 M103.95 135.41 C145.52 86.13, 188.77 38.68, 220.07 1.83 M108.93 135.77 C131.74 110.52, 156.29 82.4, 225.06 2.19 M108.93 135.77 C155.58 84.93, 200.54 32.47, 225.06 2.19 M114.58 135.38 C157.28 86.99, 201.96 37.19, 230.7 1.79 M114.58 135.38 C152.78 93.11, 190.04 49.2, 230.7 1.79 M119.56 135.74 C147.35 100.73, 175.19 69.8, 235.69 2.15 M119.56 135.74 C152.35 99.22, 182.76 63.91, 235.69 2.15 M125.21 135.34 C151.37 104.17, 174.15 75, 241.33 1.76 M125.21 135.34 C170.53 85.3, 211.95 36.33, 241.33 1.76 M130.19 135.7 C173.94 86.44, 220.64 33.18, 246.32 2.12 M130.19 135.7 C172.93 86.98, 216.51 38.35, 246.32 2.12 M135.84 135.31 C174.21 90.08, 208.21 49.75, 251.96 1.72 M135.84 135.31 C158.72 109.66, 182.85 82.09, 251.96 1.72 M140.82 135.67 C177.31 93.41, 212.97 53.24, 256.95 2.08 M140.82 135.67 C166.68 105.14, 194.13 75.79, 256.95 2.08 M146.47 135.27 C182.12 93.41, 217.19 51.75, 262.59 1.69 M146.47 135.27 C192.8 83.11, 239.07 30.19, 262.59 1.69 M151.45 135.63 C195.86 84.04, 242.39 29.83, 267.58 2.05 M151.45 135.63 C182.45 96.24, 215.98 59.61, 267.58 2.05 M157.1 135.24 C193.93 90.8, 230.49 48.73, 272.56 2.41 M157.1 135.24 C186.1 101.92, 213.74 67.58, 272.56 2.41 M162.08 135.6 C204.71 88.09, 242.57 43.67, 278.21 2.01 M162.08 135.6 C195.51 97.13, 227.49 59.04, 278.21 2.01 M167.73 135.2 C191.02 109.31, 213.95 82.1, 283.19 2.37 M167.73 135.2 C208.32 87.02, 247.72 39.69, 283.19 2.37 M172.71 135.56 C211.05 92.35, 249.27 45.71, 288.84 1.98 M172.71 135.56 C204.27 99.8, 232.12 66.33, 288.84 1.98 M178.36 135.17 C208.57 98.05, 240.33 61.56, 293.82 2.34 M178.36 135.17 C224.01 83.75, 267.9 32.95, 293.82 2.34 M183.34 135.53 C211.9 102.21, 241.28 69.57, 299.47 1.94 M183.34 135.53 C225.07 89.34, 264.88 43.51, 299.47 1.94 M188.33 135.89 C232.49 86.72, 275.85 35.32, 304.45 2.3 M188.33 135.89 C226.55 88.72, 267.12 42.68, 304.45 2.3 M193.97 135.49 C226.44 96.44, 258.36 62.18, 310.1 1.91 M193.97 135.49 C239.79 82.46, 285.44 30.27, 310.1 1.91 M198.96 135.85 C228.28 100.09, 257.95 68.9, 315.08 2.27 M198.96 135.85 C235.09 93.52, 272.84 52.67, 315.08 2.27 M204.6 135.46 C239.68 94.74, 275.29 55.5, 320.73 1.88 M204.6 135.46 C244.23 91.28, 283.02 46.4, 320.73 1.88 M209.59 135.82 C250.03 93.16, 286.49 47.86, 325.71 2.24 M209.59 135.82 C241.67 97.03, 276.6 58.48, 325.71 2.24 M215.23 135.42 C252.93 93.98, 290.73 49.25, 331.36 1.84 M215.23 135.42 C239.46 107.48, 265.75 77.82, 331.36 1.84 M220.22 135.78 C258.31 87.12, 298.41 41.67, 336.34 2.2 M220.22 135.78 C261.35 88.79, 302.28 41.23, 336.34 2.2 M225.86 135.39 C247.19 109.85, 271.45 83.37, 341.99 1.81 M225.86 135.39 C269.09 87.75, 311.09 39.46, 341.99 1.81 M230.85 135.75 C271.9 89.87, 311.65 42.41, 346.97 2.17 M230.85 135.75 C266.69 94.9, 300.46 56.72, 346.97 2.17 M236.49 135.36 C279.54 90.41, 318.34 43.37, 352.62 1.77 M236.49 135.36 C262.51 106.22, 289.8 75.87, 352.62 1.77 M241.48 135.72 C270.62 102.2, 297.59 68.43, 357.6 2.13 M241.48 135.72 C267.4 106.57, 291.68 77.77, 357.6 2.13 M247.12 135.32 C272.55 104.17, 301.27 75.42, 363.25 1.74 M247.12 135.32 C286.54 88.24, 325.79 43.89, 363.25 1.74 M252.11 135.68 C285.8 94.95, 321.96 52.06, 368.23 2.1 M252.11 135.68 C286.48 98.48, 319.42 59.27, 368.23 2.1 M257.75 135.29 C287.3 103.83, 313.13 73.67, 373.88 1.7 M257.75 135.29 C296.47 90.19, 336.96 43.61, 373.88 1.7 M262.74 135.65 C294.04 101.96, 321.68 67.42, 378.86 2.06 M262.74 135.65 C285.88 109.26, 307.99 80.92, 378.86 2.06 M268.38 135.25 C301.94 102.32, 329.68 63.48, 383.85 2.42 M268.38 135.25 C312.43 84.8, 355.81 34.99, 383.85 2.42 M273.37 135.61 C307.98 93.43, 343.82 50.19, 389.49 2.03 M273.37 135.61 C314.49 86.55, 355.98 39.87, 389.49 2.03 M279.01 135.22 C315.47 96.77, 347.58 56.5, 394.48 2.39 M279.01 135.22 C310.17 98.46, 342.48 62.75, 394.48 2.39 M284 135.58 C309.56 103.84, 338.63 75.13, 400.12 1.99 M284 135.58 C310.36 104.37, 338.59 71.89, 400.12 1.99 M289.64 135.18 C312.79 106.98, 337.43 80.83, 405.11 2.35 M289.64 135.18 C334.14 86.06, 378.85 34.51, 405.11 2.35 M294.63 135.54 C322.11 103.4, 351.85 69.91, 410.75 1.96 M294.63 135.54 C321.72 103.68, 349.75 71.49, 410.75 1.96 M299.62 135.9 C347.67 83.43, 392.82 28.41, 415.74 2.32 M299.62 135.9 C340.13 91.24, 379.75 47.11, 415.74 2.32 M305.26 135.51 C337.44 97.55, 370.85 61.54, 421.38 1.93 M305.26 135.51 C331.72 105.39, 358.21 73.88, 421.38 1.93 M310.25 135.87 C347.77 87.67, 388.48 42.7, 426.37 2.29 M310.25 135.87 C343.24 98.26, 374.33 62.37, 426.37 2.29 M315.89 135.47 C358.33 83.2, 405.58 35.13, 432.01 1.89 M315.89 135.47 C342.14 103.92, 367.01 74.44, 432.01 1.89 M320.88 135.83 C349.09 105.42, 377.17 73.43, 437 2.25 M320.88 135.83 C348.8 103.87, 377.25 71.06, 437 2.25 M326.52 135.44 C357.37 98.83, 392.96 61.44, 442.64 1.86 M326.52 135.44 C359.38 95.45, 395.35 54.96, 442.64 1.86 M331.51 135.8 C371.1 90.29, 414.52 41.02, 447.63 2.22 M331.51 135.8 C375.19 84.86, 419.5 34.87, 447.63 2.22 M337.15 135.41 C360.41 108.49, 389.11 78.17, 452.62 2.58 M337.15 135.41 C365.83 101.31, 393.5 68.62, 452.62 2.58 M342.14 135.77 C374.92 95.35, 408.22 55.28, 456.95 3.69 M342.14 135.77 C375.97 95.74, 410.85 54.51, 456.95 3.69 M347.78 135.37 C386.27 93.45, 423.41 49.67, 461.94 4.05 M347.78 135.37 C388.88 89.16, 428.69 44.23, 461.94 4.05 M352.77 135.73 C391.81 93.38, 429.55 47.99, 464.96 6.68 M352.77 135.73 C395.76 88.88, 437.51 38.86, 464.96 6.68 M358.41 135.34 C384.9 101.59, 410.76 73.18, 469.29 7.79 M358.41 135.34 C381.72 110.3, 403.42 84.85, 469.29 7.79 M363.4 135.7 C391.38 101.39, 419.01 68.91, 471.65 11.17 M363.4 135.7 C388 108.1, 412.92 77.82, 471.65 11.17 M369.04 135.3 C402.56 98.71, 434.28 59.98, 474.67 13.79 M369.04 135.3 C400.06 99.18, 430.91 65.19, 474.67 13.79 M374.03 135.66 C409.08 94.39, 445.61 50.9, 475.72 18.68 M374.03 135.66 C395.27 111.63, 415.98 87.61, 475.72 18.68 M379.67 135.27 C413.73 94.24, 448.47 58.31, 477.43 22.82 M379.67 135.27 C399.49 110.9, 420.37 87.43, 477.43 22.82 M384.66 135.63 C405.61 110.26, 426.94 85.23, 479.13 26.95 M384.66 135.63 C415.84 98.51, 447.61 62.33, 479.13 26.95 M390.3 135.23 C419.31 104.53, 444.38 75.12, 479.53 32.59 M390.3 135.23 C411.06 109.85, 433.81 86.46, 479.53 32.59 M395.29 135.59 C423.34 101.52, 451.07 71.77, 479.27 38.99 M395.29 135.59 C425.27 99.57, 456.57 63.69, 479.27 38.99 M400.93 135.2 C416.38 116.25, 434.8 96.63, 479 45.39 M400.93 135.2 C430.56 100.88, 460.84 68, 479 45.39 M405.92 135.56 C424.18 114.12, 445.92 87.99, 479.4 51.03 M405.92 135.56 C430.48 106.84, 457.04 77.45, 479.4 51.03 M410.91 135.92 C425.56 119.11, 439.25 103.89, 479.14 57.43 M410.91 135.92 C432.34 111.55, 454.95 85.96, 479.14 57.43 M416.55 135.52 C437.03 112.31, 458.69 88.33, 479.53 63.07 M416.55 135.52 C441.28 108.64, 464.07 81.07, 479.53 63.07 M421.54 135.88 C439.7 114.89, 456.47 92.93, 479.27 69.47 M421.54 135.88 C433.45 119.98, 448.34 104.83, 479.27 69.47 M427.18 135.49 C444.19 117.51, 459.72 100.42, 479.66 75.11 M427.18 135.49 C444.66 114.13, 462.3 93.68, 479.66 75.11 M432.17 135.85 C446.83 119.86, 461.29 103.69, 479.4 81.51 M432.17 135.85 C442.13 123.55, 452.74 111.76, 479.4 81.51 M437.81 135.46 C448.92 124.72, 457.92 112.62, 479.8 87.15 M437.81 135.46 C453.55 117.76, 467.68 101.47, 479.8 87.15 M445.42 132.8 C459.39 120.23, 469.27 105.41, 479.54 93.55 M445.42 132.8 C454.06 123.31, 462.94 111.17, 479.54 93.55 M451.06 132.4 C463.15 119.27, 472.64 110.16, 477.96 101.46 M451.06 132.4 C458.03 124.07, 464.8 115.65, 477.96 101.46 M457.36 131.25 C462.65 126.55, 467.85 119.82, 477.05 108.61 M457.36 131.25 C462.56 125.74, 468.27 120.23, 477.05 108.61 M464.97 128.59 C469.23 123.21, 474.5 119.94, 476.13 115.76 M464.97 128.59 C466.4 126.62, 470.09 122.46, 476.13 115.76" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C134.76 -0.82, 237.15 -2.84, 445 0 M32 0 C117.14 0.77, 203.3 0.82, 445 0 M445 0 C464.46 1.31, 477.82 10, 477 32 M445 0 C468.12 -1.15, 476.89 8.73, 477 32 M477 32 C477.29 57.75, 476.77 81.02, 477 101 M477 32 C477.02 52.99, 476.55 76.44, 477 101 M477 101 C476.76 123.18, 464.89 132.41, 445 133 M477 101 C479.01 122.87, 468.12 132.27, 445 133 M445 133 C321.26 133.45, 195.94 134.33, 32 133 M445 133 C294.67 133.48, 144.05 133.68, 32 133 M32 133 C9.9 132.01, -1.58 121.23, 0 101 M32 133 C9.79 134.41, -1.71 120.88, 0 101 M0 101 C-1.18 83.33, -0.4 65.06, 0 32 M0 101 C0 80.69, 0.41 59.87, 0 32 M0 32 C1.82 10.89, 11.97 0.59, 32 0 M0 32 C-1.7 8.91, 9.45 -0.01, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(805.94921875 75.3515625) rotate(0 220.3125 57.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> symlink:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> name: 00000000000000000000000000000000-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> target: /foo</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โฆ</text></g><g stroke-linecap="round" transform="translate(806.46875 225.2578125) rotate(0 238.5 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.68 15.22, 9.53 10.88, 18.1 2.48 M3.66 19.09 C8.81 14.12, 11.51 9.97, 18.1 2.48 M2.75 26.24 C6.81 18.96, 11.51 16.19, 24.4 1.33 M2.75 26.24 C8.1 20.93, 12.69 14.7, 24.4 1.33 M2.48 32.64 C12.09 22.75, 21.05 8.83, 30.7 0.18 M2.48 32.64 C12.24 21.36, 23.11 8.33, 30.7 0.18 M2.88 38.28 C14.38 23.94, 25.95 11.59, 34.37 2.05 M2.88 38.28 C9.83 28.87, 18.57 19.97, 34.37 2.05 M2.62 44.68 C11.2 34.98, 16.8 26.09, 39.36 2.41 M2.62 44.68 C9.89 35.67, 19.19 25.97, 39.36 2.41 M2.36 51.07 C16.6 33.87, 30.06 19.6, 45 2.02 M2.36 51.07 C14.11 36.43, 26.4 24.25, 45 2.02 M2.75 56.72 C11.38 44.5, 23.52 31.72, 49.99 2.38 M2.75 56.72 C12.11 44.16, 21.93 34.17, 49.99 2.38 M2.49 63.11 C22.69 39.21, 44.13 13.8, 55.63 1.98 M2.49 63.11 C24.61 38.46, 45.21 14.7, 55.63 1.98 M2.88 68.76 C22.62 46.66, 38.87 25.27, 60.62 2.34 M2.88 68.76 C15.25 52.49, 29.63 37.02, 60.62 2.34 M2.62 75.16 C19.95 55.49, 39.17 32.32, 66.26 1.95 M2.62 75.16 C24.54 50.58, 44.69 26.49, 66.26 1.95 M2.36 81.55 C27.08 53.26, 56.1 20.67, 71.25 2.31 M2.36 81.55 C22.38 59.12, 42.38 37.91, 71.25 2.31 M2.76 87.2 C16.85 69.7, 34.99 53.54, 76.89 1.91 M2.76 87.2 C24.69 60.73, 48.08 33.35, 76.89 1.91 M2.49 93.59 C26.83 67.24, 48.57 42.72, 81.88 2.27 M2.49 93.59 C22.01 71.75, 40.83 49.63, 81.88 2.27 M2.89 99.24 C21.26 75.44, 42.16 55.2, 87.52 1.88 M2.89 99.24 C27.49 71.35, 50.92 41.12, 87.52 1.88 M2.63 105.64 C30.05 76.71, 56.77 44.63, 92.51 2.24 M2.63 105.64 C29.93 73.84, 55.66 42.59, 92.51 2.24 M2.37 112.03 C31.19 75.8, 63.69 43.11, 98.15 1.85 M2.37 112.03 C33.09 77.93, 61.62 44.45, 98.15 1.85 M2.76 117.68 C39.28 75.49, 75.08 36.84, 103.14 2.21 M2.76 117.68 C38.87 78.04, 74.17 36.06, 103.14 2.21 M3.16 123.32 C44.92 78.9, 86.85 29.88, 108.78 1.81 M3.16 123.32 C33.5 86.75, 64.72 49.98, 108.78 1.81 M4.86 127.45 C37.1 91.22, 67.62 56.08, 113.77 2.17 M4.86 127.45 C48.91 78.36, 90.69 30.13, 113.77 2.17 M5.26 133.1 C28.92 105.27, 51.55 78.45, 119.41 1.78 M5.26 133.1 C39.84 93.11, 73.53 54.29, 119.41 1.78 M7.62 136.47 C35.56 105.24, 62.69 73.44, 124.4 2.14 M7.62 136.47 C41.94 98.75, 76.48 57.38, 124.4 2.14 M10.64 139.1 C45.2 102.3, 79.7 60.38, 130.04 1.74 M10.64 139.1 C39.87 104.89, 70.76 71.45, 130.04 1.74 M14.31 140.97 C43.51 110.97, 68.26 78.62, 135.03 2.1 M14.31 140.97 C45.94 106.35, 77.72 71.79, 135.03 2.1 M17.33 143.59 C63.36 91.09, 109.79 35.17, 140.67 1.71 M17.33 143.59 C67.02 87.83, 115.79 32.77, 140.67 1.71 M21.01 145.46 C69.52 85.94, 122.53 32.68, 145.66 2.07 M21.01 145.46 C65.52 93.39, 110.85 41.72, 145.66 2.07 M25.34 146.58 C73.82 90.55, 124.58 31.62, 150.65 2.43 M25.34 146.58 C57.27 108.71, 90.6 71.59, 150.65 2.43 M32.29 144.67 C82.37 88.85, 130.86 32.77, 156.29 2.03 M32.29 144.67 C58.07 117.01, 83.71 85.72, 156.29 2.03 M37.28 145.03 C76.55 100.57, 116.26 57.79, 161.28 2.39 M37.28 145.03 C75.27 101.67, 112.08 57.69, 161.28 2.39 M42.92 144.64 C87.59 96.54, 128.11 43.6, 166.92 2 M42.92 144.64 C76.77 106.18, 112.16 66.71, 166.92 2 M47.91 145 C74.27 114.89, 99.04 86.08, 171.91 2.36 M47.91 145 C80.84 110.75, 111.51 74.33, 171.91 2.36 M52.9 145.36 C91.05 102.58, 127.57 62.7, 177.55 1.96 M52.9 145.36 C99.51 92.64, 144.44 40.01, 177.55 1.96 M58.54 144.96 C96.62 103.36, 132.1 61.4, 182.54 2.32 M58.54 144.96 C102.3 95.31, 145 44.66, 182.54 2.32 M63.53 145.32 C109.82 92.88, 153.81 37.96, 188.18 1.93 M63.53 145.32 C106.42 95.64, 150.95 44.77, 188.18 1.93 M69.17 144.93 C96.18 113.75, 124.05 81.34, 193.17 2.29 M69.17 144.93 C108.27 96.79, 149.34 50.87, 193.17 2.29 M74.16 145.29 C97.88 116.26, 126.66 87.16, 198.81 1.9 M74.16 145.29 C103.75 113.96, 133.25 80.61, 198.81 1.9 M79.8 144.9 C128.87 85.82, 177.7 31.59, 203.8 2.26 M79.8 144.9 C108.17 113.17, 135.25 78.83, 203.8 2.26 M84.79 145.26 C128.13 96.77, 168.82 46.48, 209.44 1.86 M84.79 145.26 C120.57 103.55, 156.93 61.55, 209.44 1.86 M90.43 144.86 C131.03 103.87, 164.62 58.8, 214.43 2.22 M90.43 144.86 C139.02 86.88, 188.33 30.13, 214.43 2.22 M95.42 145.22 C122.03 112.94, 150.45 84.35, 220.07 1.83 M95.42 145.22 C135.79 98.82, 175.14 53.71, 220.07 1.83 M101.06 144.83 C140.89 97.19, 181.91 54.17, 225.06 2.19 M101.06 144.83 C134.98 105.95, 167.66 68.29, 225.06 2.19 M106.05 145.19 C152.49 92.82, 198.26 40.96, 230.7 1.79 M106.05 145.19 C132.37 114.56, 159.47 83.34, 230.7 1.79 M111.69 144.79 C153.52 93.02, 197.5 42.26, 235.69 2.15 M111.69 144.79 C159.86 91.23, 206.41 36.81, 235.69 2.15 M116.68 145.15 C142.1 113.42, 167.54 83.21, 241.33 1.76 M116.68 145.15 C161.14 92.66, 207.79 41.99, 241.33 1.76 M122.32 144.76 C166.13 93.13, 209.82 39.8, 246.32 2.12 M122.32 144.76 C166.64 93.28, 213.29 39.18, 246.32 2.12 M127.31 145.12 C165.51 100.3, 206.72 56.81, 251.96 1.72 M127.31 145.12 C156.34 112.26, 187.1 77.6, 251.96 1.72 M132.95 144.72 C166.91 109.34, 195.98 71.92, 256.95 2.08 M132.95 144.72 C180.38 89.53, 229.12 34.54, 256.95 2.08 M137.94 145.08 C175.38 101.81, 210.1 63.31, 262.59 1.69 M137.94 145.08 C169.66 108.24, 200.14 72.75, 262.59 1.69 M143.58 144.69 C168.18 114.88, 194.29 86.3, 267.58 2.05 M143.58 144.69 C176.83 108.94, 208.19 70.29, 267.58 2.05 M148.57 145.05 C182.56 103.55, 216.29 66.03, 272.56 2.41 M148.57 145.05 C181.02 105.11, 215.28 67.56, 272.56 2.41 M154.21 144.65 C189.78 103.53, 226.3 57.46, 278.21 2.01 M154.21 144.65 C194.19 98.45, 235.22 50.5, 278.21 2.01 M159.2 145.01 C201.39 97.64, 239.44 52.54, 283.19 2.37 M159.2 145.01 C196.58 101.2, 234.19 59.04, 283.19 2.37 M164.19 145.37 C204.83 100.56, 245.77 53.99, 288.84 1.98 M164.19 145.37 C194.6 111.64, 224.28 77.56, 288.84 1.98 M169.83 144.98 C216.09 96.8, 258.65 45.93, 293.82 2.34 M169.83 144.98 C198.36 111.93, 225.45 82.13, 293.82 2.34 M174.82 145.34 C222.13 92.22, 266.76 39.3, 299.47 1.94 M174.82 145.34 C214.43 98.2, 254.01 52.59, 299.47 1.94 M180.46 144.94 C206.13 115.61, 232.02 85.17, 304.45 2.3 M180.46 144.94 C208.14 111.98, 236.78 80.02, 304.45 2.3 M185.45 145.31 C211.73 114.99, 239.84 81.36, 310.1 1.91 M185.45 145.31 C211.68 114.43, 238.14 82.89, 310.1 1.91 M191.09 144.91 C218.08 111.04, 246.8 80.32, 315.08 2.27 M191.09 144.91 C214.94 114.57, 239.96 87.29, 315.08 2.27 M196.08 145.27 C239.46 94.21, 286.41 44.83, 320.73 1.88 M196.08 145.27 C229.6 106.98, 264.69 68.01, 320.73 1.88 M201.72 144.88 C228.54 113.1, 258.25 77.79, 325.71 2.24 M201.72 144.88 C243.4 96.39, 285.56 47.13, 325.71 2.24 M206.71 145.24 C253.76 92.02, 298.3 40.23, 331.36 1.84 M206.71 145.24 C244.96 99.96, 284.86 52.62, 331.36 1.84 M212.35 144.84 C256.46 90.35, 305.16 39.74, 336.34 2.2 M212.35 144.84 C254.88 97.2, 294.9 51.16, 336.34 2.2 M217.34 145.2 C260.85 95.12, 303.44 46.91, 341.99 1.81 M217.34 145.2 C254.9 102.38, 292.7 59.98, 341.99 1.81 M222.98 144.81 C255.34 105.33, 290.89 65.64, 346.97 2.17 M222.98 144.81 C266.33 93.19, 310.85 42.12, 346.97 2.17 M227.97 145.17 C263.66 108.29, 298 68.71, 352.62 1.77 M227.97 145.17 C258.23 109.67, 288.39 75.4, 352.62 1.77 M233.61 144.77 C270.05 104.52, 304.97 63.07, 357.6 2.13 M233.61 144.77 C274.47 98.07, 312.89 55.02, 357.6 2.13 M238.6 145.13 C266.67 111.74, 296.18 82.47, 363.25 1.74 M238.6 145.13 C277.04 101.17, 315.84 55.8, 363.25 1.74 M244.24 144.74 C274.9 111.46, 304.37 75.97, 368.23 2.1 M244.24 144.74 C293.37 88.52, 341.04 33.09, 368.23 2.1 M249.23 145.1 C276.79 116.75, 302.14 86.51, 373.88 1.7 M249.23 145.1 C290.94 99.98, 331.11 52.85, 373.88 1.7 M254.87 144.7 C287.13 105.19, 322.18 68.96, 378.86 2.06 M254.87 144.7 C288.12 105.83, 322.32 65.46, 378.86 2.06 M259.86 145.06 C297.18 103.73, 332.56 62.14, 383.85 2.42 M259.86 145.06 C285.47 114.28, 312.5 83.14, 383.85 2.42 M265.5 144.67 C289.04 113.22, 315.35 85.99, 389.49 2.03 M265.5 144.67 C309.3 94.62, 352.05 44.75, 389.49 2.03 M270.49 145.03 C300.51 108.19, 332.82 72.1, 394.48 2.39 M270.49 145.03 C300.97 111.01, 330.93 74.5, 394.48 2.39 M276.13 144.63 C324.36 91.02, 372.28 35.59, 400.12 1.99 M276.13 144.63 C314.28 100.64, 353.55 56.58, 400.12 1.99 M281.12 144.99 C306.5 114.55, 332.17 85.71, 405.11 2.35 M281.12 144.99 C323.82 96.38, 365.49 47.59, 405.11 2.35 M286.1 145.35 C317.3 110.79, 350.4 75.24, 410.75 1.96 M286.1 145.35 C329.26 94.11, 373.2 45.44, 410.75 1.96 M291.75 144.96 C331.46 97.55, 372.96 52.2, 415.74 2.32 M291.75 144.96 C333.75 97.37, 374.48 47.84, 415.74 2.32 M296.73 145.32 C339.74 99.77, 378.75 49.03, 421.38 1.93 M296.73 145.32 C339.27 96.73, 382.31 46.86, 421.38 1.93 M302.38 144.93 C334.79 106.51, 370.65 62.67, 426.37 2.29 M302.38 144.93 C351.86 88.93, 398.72 33.88, 426.37 2.29 M307.36 145.29 C340.53 106.77, 376.08 66.7, 432.01 1.89 M307.36 145.29 C343.7 102.77, 379.62 59.95, 432.01 1.89 M313.01 144.89 C349.35 105.7, 384.17 63.87, 437 2.25 M313.01 144.89 C353.04 98.04, 391.67 55.11, 437 2.25 M317.99 145.25 C339.84 114.7, 366.5 88.66, 442.64 1.86 M317.99 145.25 C351.57 103.84, 387.73 63.05, 442.64 1.86 M323.64 144.86 C348.5 114.38, 375.72 88.33, 447.63 2.22 M323.64 144.86 C355.53 106.62, 389.28 69.15, 447.63 2.22 M328.62 145.22 C372.38 92.62, 416.87 43.42, 452.62 2.58 M328.62 145.22 C366.51 102.9, 405.23 58.43, 452.62 2.58 M334.27 144.82 C363.07 113.17, 389.9 79.51, 456.95 3.69 M334.27 144.82 C367.21 104.1, 402.44 65.76, 456.95 3.69 M339.25 145.18 C372.8 105.31, 407.19 67.1, 461.94 4.05 M339.25 145.18 C370.71 111.13, 401.08 74.54, 461.94 4.05 M344.9 144.79 C373.24 109.42, 403.66 74.09, 464.96 6.68 M344.9 144.79 C380.58 103.48, 416.5 62.22, 464.96 6.68 M349.88 145.15 C396.98 89.22, 441.52 35.96, 469.29 7.79 M349.88 145.15 C384.63 103.87, 421.1 63.92, 469.29 7.79 M355.53 144.75 C384.31 114.2, 411.66 82.06, 471.65 11.17 M355.53 144.75 C395.88 99.08, 436.7 51.19, 471.65 11.17 M360.51 145.11 C388.54 111.66, 414.89 82.27, 474.67 13.79 M360.51 145.11 C398.58 98.72, 438.1 53.79, 474.67 13.79 M366.16 144.72 C399.89 104.57, 433.88 66.68, 475.72 18.68 M366.16 144.72 C408.72 95.09, 451.17 47.53, 475.72 18.68 M371.14 145.08 C397.64 114.94, 427.94 82.11, 477.43 22.82 M371.14 145.08 C410.48 97.21, 450.83 51.08, 477.43 22.82 M376.79 144.68 C397.46 116.7, 419.98 91.38, 479.13 26.95 M376.79 144.68 C400.93 117.38, 426.7 88.5, 479.13 26.95 M381.77 145.04 C417.98 100.88, 455.83 57.26, 479.53 32.59 M381.77 145.04 C410.94 114.25, 438.41 81.7, 479.53 32.59 M387.42 144.65 C412.93 114.6, 438.57 87, 479.27 38.99 M387.42 144.65 C411.38 118.3, 434.26 90.98, 479.27 38.99 M392.4 145.01 C416.3 120.47, 436.57 92.84, 479 45.39 M392.4 145.01 C415.9 120.64, 438.88 93.42, 479 45.39 M397.39 145.37 C415.88 120.12, 437.69 100.01, 479.4 51.03 M397.39 145.37 C419.06 121.26, 438.71 97.02, 479.4 51.03 M403.03 144.98 C418.65 128.5, 434.02 108.59, 479.14 57.43 M403.03 144.98 C431.82 109.79, 463.03 76.44, 479.14 57.43 M408.02 145.34 C423.72 128.07, 441.93 108.83, 479.53 63.07 M408.02 145.34 C423.27 129.37, 437.27 112.48, 479.53 63.07 M413.66 144.94 C430.89 123.63, 448.67 104.59, 479.27 69.47 M413.66 144.94 C429.3 124.99, 447.94 106.36, 479.27 69.47 M418.65 145.3 C429.88 130.69, 442.79 115.76, 479.66 75.11 M418.65 145.3 C438.44 123.35, 457.66 100.29, 479.66 75.11 M424.29 144.91 C443.94 120.3, 467.31 96.47, 479.4 81.51 M424.29 144.91 C443.54 122.53, 464.39 98.64, 479.4 81.51 M429.28 145.27 C440.46 128.53, 453.86 117.18, 479.8 87.15 M429.28 145.27 C446.57 125.43, 465.7 103.67, 479.8 87.15 M434.92 144.87 C452.62 126.78, 470.9 107.72, 479.54 93.55 M434.92 144.87 C446.21 130.49, 458.92 116.32, 479.54 93.55 M439.91 145.23 C454.92 126.59, 470.52 110.64, 479.93 99.19 M439.91 145.23 C454.48 130.43, 466.98 114.01, 479.93 99.19 M445.55 144.84 C459.7 129.63, 473.4 112.45, 479.67 105.59 M445.55 144.84 C456.2 132.48, 466.43 120.71, 479.67 105.59 M451.2 144.44 C457.4 137.27, 463.87 127.4, 478.1 113.5 M451.2 144.44 C457.13 137.53, 462.11 130.98, 478.1 113.5 M457.5 143.29 C464.19 138.07, 467.02 131.58, 476.52 121.41 M457.5 143.29 C461.1 138.38, 465.47 132.12, 476.52 121.41 M465.11 140.63 C469.09 135.97, 469.57 134.23, 476.26 127.8 M465.11 140.63 C467.57 137.52, 470.45 135.62, 476.26 127.8" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C176.04 -2.14, 317.57 -1.78, 445 0 M32 0 C179.36 0.19, 326.13 -0.1, 445 0 M445 0 C466.49 -1.62, 476.74 11.76, 477 32 M445 0 C465.33 0.95, 478.9 10.46, 477 32 M477 32 C478.38 59.54, 477.89 83.51, 477 113 M477 32 C477.4 63.12, 476.78 93.95, 477 113 M477 113 C476.1 134.31, 466.7 143.02, 445 145 M477 113 C478.22 135.1, 467.61 144.46, 445 145 M445 145 C356.12 145.98, 265.85 145.1, 32 145 M445 145 C280.4 145.67, 115.39 145.75, 32 145 M32 145 C8.74 143.12, -0.92 132.86, 0 113 M32 145 C12.94 142.98, -0.76 132.33, 0 113 M0 113 C-0.07 82.2, 0.07 49.78, 0 32 M0 113 C0.34 82.21, 0.32 51.58, 0 32 M0 32 C1.6 12.66, 10.66 -1.83, 32 0 M0 32 C1.88 10.07, 10.34 0.34, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(811.46875 230.2578125) rotate(0 220.3125 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> directory:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> name: 11111111111111111111111111111111-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <directory-complicated-digest></text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 4</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โฆ</text></g><g stroke-linecap="round" transform="translate(810.1484375 394.59375) rotate(0 238.5 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.91 13.37, 11.17 9.44, 18.1 2.48 M3.66 19.09 C9.16 12.46, 14.4 7.69, 18.1 2.48 M2.75 26.24 C9.7 18.96, 16.37 8.53, 24.4 1.33 M2.75 26.24 C11.39 17.63, 20.3 6.99, 24.4 1.33 M2.48 32.64 C10.71 24.03, 18.59 13.26, 30.7 0.18 M2.48 32.64 C11.25 23.35, 19.02 14.4, 30.7 0.18 M2.88 38.28 C16.41 22.39, 27.21 9.65, 34.37 2.05 M2.88 38.28 C9.5 30.63, 15.35 23.52, 34.37 2.05 M2.62 44.68 C14.02 30.56, 24.17 18.77, 39.36 2.41 M2.62 44.68 C11.44 34.89, 20.03 24.71, 39.36 2.41 M2.36 51.07 C14.17 38.91, 27.26 20.6, 45 2.02 M2.36 51.07 C14.79 38.21, 27.2 22.82, 45 2.02 M2.75 56.72 C14.28 42.93, 28 31.31, 49.99 2.38 M2.75 56.72 C14.37 45.04, 23.83 32.21, 49.99 2.38 M2.49 63.11 C15.91 47.14, 31.6 33.09, 55.63 1.98 M2.49 63.11 C22.3 40.56, 42.25 16.35, 55.63 1.98 M2.88 68.76 C25.94 42.37, 49.23 17.68, 60.62 2.34 M2.88 68.76 C25.29 41.12, 49.97 16.59, 60.62 2.34 M2.62 75.16 C26.36 49.35, 49.71 23.45, 66.26 1.95 M2.62 75.16 C27.26 46.7, 53.03 16.79, 66.26 1.95 M2.36 81.55 C18.75 60.1, 37.31 41.18, 71.25 2.31 M2.36 81.55 C30.19 50.57, 57.16 19.4, 71.25 2.31 M2.76 87.2 C17.36 71.88, 32.62 50.73, 76.89 1.91 M2.76 87.2 C26.3 60.71, 49.92 34.99, 76.89 1.91 M2.49 93.59 C27.25 66.48, 49.52 37.6, 81.88 2.27 M2.49 93.59 C30.85 62.58, 57.18 29.13, 81.88 2.27 M2.89 99.24 C25.68 73.59, 51.19 46.74, 87.52 1.88 M2.89 99.24 C20.96 78.77, 38.14 58.85, 87.52 1.88 M2.63 105.64 C27.26 82.03, 48.77 55.01, 92.51 2.24 M2.63 105.64 C29.71 75, 56.08 45.73, 92.51 2.24 M2.37 112.03 C38.89 71.73, 73.23 30.36, 98.15 1.85 M2.37 112.03 C31.31 79.87, 58.69 47.79, 98.15 1.85 M2.76 117.68 C38.65 77.93, 73.02 35.37, 103.14 2.21 M2.76 117.68 C39.96 75.26, 75.95 31.69, 103.14 2.21 M3.16 123.32 C39.16 81.11, 77.97 37.18, 108.78 1.81 M3.16 123.32 C26.62 96.63, 50.07 69.49, 108.78 1.81 M4.86 127.45 C38.16 84.21, 75.04 44.41, 113.77 2.17 M4.86 127.45 C26.16 101.72, 50.12 76.16, 113.77 2.17 M5.26 133.1 C32.36 105, 60.22 74.13, 119.41 1.78 M5.26 133.1 C49.95 79.53, 94.95 28.74, 119.41 1.78 M7.62 136.47 C35.36 107.28, 59.43 73.22, 124.4 2.14 M7.62 136.47 C48.07 90.66, 87.09 44.03, 124.4 2.14 M10.64 139.1 C44.23 99.15, 78.94 58.88, 130.04 1.74 M10.64 139.1 C48.85 99.23, 82.98 56.76, 130.04 1.74 M14.31 140.97 C61.07 83.76, 109.13 28.41, 135.03 2.1 M14.31 140.97 C40.23 110.15, 67.08 81.31, 135.03 2.1 M17.33 143.59 C57.88 96.8, 95.53 53.8, 140.67 1.71 M17.33 143.59 C57.08 96.93, 97.56 52.45, 140.67 1.71 M21.01 145.46 C56.01 106.76, 88.35 69.45, 145.66 2.07 M21.01 145.46 C67.32 93, 112.76 41.13, 145.66 2.07 M25.34 146.58 C51.39 115.65, 78.49 84.38, 150.65 2.43 M25.34 146.58 C67.89 94.87, 112.13 43.89, 150.65 2.43 M32.29 144.67 C81.15 91.53, 127.11 36.9, 156.29 2.03 M32.29 144.67 C57.68 113.6, 83.65 83.25, 156.29 2.03 M37.28 145.03 C80.72 91.85, 128.85 42.46, 161.28 2.39 M37.28 145.03 C81.17 93.73, 125.7 40.99, 161.28 2.39 M42.92 144.64 C86.35 93.9, 133.36 38.98, 166.92 2 M42.92 144.64 C81.83 99.95, 121.66 56.09, 166.92 2 M47.91 145 C76.08 112.91, 107.55 78.18, 171.91 2.36 M47.91 145 C81.06 109.66, 111.35 72.82, 171.91 2.36 M52.9 145.36 C100.16 89.62, 149.95 34.51, 177.55 1.96 M52.9 145.36 C89.79 102.8, 125.11 62.89, 177.55 1.96 M58.54 144.96 C90.83 107.72, 120.89 72.28, 182.54 2.32 M58.54 144.96 C83.32 116.05, 108.95 87.3, 182.54 2.32 M63.53 145.32 C97.39 110.22, 128.17 69.65, 188.18 1.93 M63.53 145.32 C97.52 104.23, 131.94 65.28, 188.18 1.93 M69.17 144.93 C100.97 104.36, 136.13 67.64, 193.17 2.29 M69.17 144.93 C105.44 102.82, 142.12 58.58, 193.17 2.29 M74.16 145.29 C113.39 99.23, 154.87 50.02, 198.81 1.9 M74.16 145.29 C115.68 97.91, 154.99 52.02, 198.81 1.9 M79.8 144.9 C117.22 100.44, 155.63 58.46, 203.8 2.26 M79.8 144.9 C119.55 99.87, 159.99 53.61, 203.8 2.26 M84.79 145.26 C114.96 111.31, 144.84 77.07, 209.44 1.86 M84.79 145.26 C130.74 96.04, 174.26 45.02, 209.44 1.86 M90.43 144.86 C119.59 111.08, 146.64 82.67, 214.43 2.22 M90.43 144.86 C137.14 92.65, 182.18 40.05, 214.43 2.22 M95.42 145.22 C134.97 97.52, 174.23 52.21, 220.07 1.83 M95.42 145.22 C121.16 115.94, 147.23 85.62, 220.07 1.83 M101.06 144.83 C128.73 111.86, 158.26 79.88, 225.06 2.19 M101.06 144.83 C127.52 114.11, 154.95 81.91, 225.06 2.19 M106.05 145.19 C132.23 115.04, 157.86 83.38, 230.7 1.79 M106.05 145.19 C133.88 111.98, 162.23 80.52, 230.7 1.79 M111.69 144.79 C135.52 113.89, 160.61 88.04, 235.69 2.15 M111.69 144.79 C155.76 94.05, 201.14 44.16, 235.69 2.15 M116.68 145.15 C149.35 106.9, 185.6 68, 241.33 1.76 M116.68 145.15 C144.46 112.95, 173.88 78.54, 241.33 1.76 M122.32 144.76 C163.47 96.75, 205.64 46.73, 246.32 2.12 M122.32 144.76 C169.21 91.48, 214.25 39.39, 246.32 2.12 M127.31 145.12 C165.46 100.62, 205.52 51.66, 251.96 1.72 M127.31 145.12 C172.07 90.92, 219.78 38.74, 251.96 1.72 M132.95 144.72 C175.8 96.08, 215.01 50.98, 256.95 2.08 M132.95 144.72 C175.65 95.27, 218.21 46.7, 256.95 2.08 M137.94 145.08 C175.05 101.58, 213.52 59.48, 262.59 1.69 M137.94 145.08 C171.08 105.35, 206.42 65.3, 262.59 1.69 M143.58 144.69 C186.3 92.98, 230.97 41.88, 267.58 2.05 M143.58 144.69 C178.59 107.36, 212.3 68.54, 267.58 2.05 M148.57 145.05 C179.15 108.79, 208.33 76.07, 272.56 2.41 M148.57 145.05 C184.5 104.35, 220.01 62.87, 272.56 2.41 M154.21 144.65 C195.56 97.04, 233.34 55.88, 278.21 2.01 M154.21 144.65 C182.1 112.15, 210.74 81.54, 278.21 2.01 M159.2 145.01 C197.45 101.68, 236.26 55.56, 283.19 2.37 M159.2 145.01 C190.06 111.73, 219.71 76.82, 283.19 2.37 M164.19 145.37 C214.12 88.29, 261.36 32.74, 288.84 1.98 M164.19 145.37 C191.17 116.65, 216.4 87.09, 288.84 1.98 M169.83 144.98 C211.86 100.8, 252.07 52.72, 293.82 2.34 M169.83 144.98 C202.9 106.38, 236.88 69.34, 293.82 2.34 M174.82 145.34 C207.63 107.06, 242.55 64.81, 299.47 1.94 M174.82 145.34 C211.8 104.01, 247.25 62.79, 299.47 1.94 M180.46 144.94 C205.47 114.55, 232.3 83.6, 304.45 2.3 M180.46 144.94 C204.45 114.46, 230.06 86.52, 304.45 2.3 M185.45 145.31 C229.35 95.12, 271.86 44.84, 310.1 1.91 M185.45 145.31 C216.37 107.9, 248.61 71.36, 310.1 1.91 M191.09 144.91 C222.55 111.72, 251.85 73.92, 315.08 2.27 M191.09 144.91 C238.55 91.08, 286.27 36.02, 315.08 2.27 M196.08 145.27 C234.29 100.34, 274.54 56.28, 320.73 1.88 M196.08 145.27 C222.42 114.67, 248.24 85.32, 320.73 1.88 M201.72 144.88 C245.02 96.69, 286.1 47.75, 325.71 2.24 M201.72 144.88 C233.01 110.5, 264.89 75.09, 325.71 2.24 M206.71 145.24 C250.09 93.17, 294.8 45.48, 331.36 1.84 M206.71 145.24 C246.95 98.07, 287.81 52.25, 331.36 1.84 M212.35 144.84 C255.02 97.82, 294.21 47.38, 336.34 2.2 M212.35 144.84 C254.78 98.81, 294.78 49.85, 336.34 2.2 M217.34 145.2 C260.68 96.29, 303.76 46.02, 341.99 1.81 M217.34 145.2 C250.78 105.78, 286.51 63.39, 341.99 1.81 M222.98 144.81 C272.96 88.46, 318.86 33.36, 346.97 2.17 M222.98 144.81 C256.46 106.72, 290.95 67.45, 346.97 2.17 M227.97 145.17 C264.79 102.33, 299.75 59.1, 352.62 1.77 M227.97 145.17 C263.52 105.07, 298.61 63.81, 352.62 1.77 M233.61 144.77 C274.12 96.97, 312.37 55.98, 357.6 2.13 M233.61 144.77 C256.24 114.88, 281.92 87.66, 357.6 2.13 M238.6 145.13 C271.28 102.8, 307.76 62.45, 363.25 1.74 M238.6 145.13 C264.06 115.44, 290.29 87.89, 363.25 1.74 M244.24 144.74 C276.08 105.66, 310.55 68.73, 368.23 2.1 M244.24 144.74 C288.01 92.91, 332.32 42.92, 368.23 2.1 M249.23 145.1 C286.85 103.31, 326.12 58.27, 373.88 1.7 M249.23 145.1 C278.25 112.14, 306.08 78.73, 373.88 1.7 M254.87 144.7 C287.46 102.93, 323.86 65.43, 378.86 2.06 M254.87 144.7 C288.68 105.04, 323.05 66.18, 378.86 2.06 M259.86 145.06 C291.28 111.25, 321.61 73.04, 383.85 2.42 M259.86 145.06 C289.7 108.87, 320.7 73.05, 383.85 2.42 M265.5 144.67 C302.2 102.1, 339.7 59.09, 389.49 2.03 M265.5 144.67 C314.09 87.39, 361.48 31.86, 389.49 2.03 M270.49 145.03 C306.79 101.72, 345.32 61.4, 394.48 2.39 M270.49 145.03 C300.53 111.68, 330.35 77.04, 394.48 2.39 M276.13 144.63 C319.2 96.52, 362.81 44.49, 400.12 1.99 M276.13 144.63 C306.01 109.16, 335.36 75.86, 400.12 1.99 M281.12 144.99 C321.76 94.08, 364.67 45.75, 405.11 2.35 M281.12 144.99 C319.22 100.17, 357.51 56.73, 405.11 2.35 M286.1 145.35 C333.71 88.53, 382.13 35.38, 410.75 1.96 M286.1 145.35 C317.78 109.04, 351.73 70.98, 410.75 1.96 M291.75 144.96 C337.19 88.4, 384.3 34.77, 415.74 2.32 M291.75 144.96 C317.91 112.26, 345.28 81.05, 415.74 2.32 M296.73 145.32 C325.68 112.17, 358.06 76.44, 421.38 1.93 M296.73 145.32 C343.84 88.98, 392.35 33.13, 421.38 1.93 M302.38 144.93 C339.95 106.31, 374.33 64.87, 426.37 2.29 M302.38 144.93 C336.03 105.32, 370.06 67.12, 426.37 2.29 M307.36 145.29 C340.16 110, 371.15 72.35, 432.01 1.89 M307.36 145.29 C340.08 109.03, 371.38 70.88, 432.01 1.89 M313.01 144.89 C346.7 110.4, 379.86 70.69, 437 2.25 M313.01 144.89 C342.6 108.31, 373.8 74.9, 437 2.25 M317.99 145.25 C351.16 108.99, 380.33 72.15, 442.64 1.86 M317.99 145.25 C343.7 117.08, 369.38 86.42, 442.64 1.86 M323.64 144.86 C370.25 87.28, 421.75 33.13, 447.63 2.22 M323.64 144.86 C351.37 114.11, 380.44 81.53, 447.63 2.22 M328.62 145.22 C355.29 117.71, 379.5 88.31, 452.62 2.58 M328.62 145.22 C362.76 106.45, 396.24 68.64, 452.62 2.58 M334.27 144.82 C363.38 107.43, 398.48 72.68, 456.95 3.69 M334.27 144.82 C357.91 116.46, 383 87.56, 456.95 3.69 M339.25 145.18 C379.04 101.04, 417.7 54.68, 461.94 4.05 M339.25 145.18 C385.54 91.6, 433.51 37.95, 461.94 4.05 M344.9 144.79 C386.94 95.92, 432.15 44.1, 464.96 6.68 M344.9 144.79 C374.61 108.52, 405.83 74.64, 464.96 6.68 M349.88 145.15 C391 98.05, 435.67 47.17, 469.29 7.79 M349.88 145.15 C395.91 94.65, 441.86 42.78, 469.29 7.79 M355.53 144.75 C385.45 107.18, 418.33 70.28, 471.65 11.17 M355.53 144.75 C400.44 93.54, 444.91 43.39, 471.65 11.17 M360.51 145.11 C401.24 102.6, 437.59 56.72, 474.67 13.79 M360.51 145.11 C405.18 92.61, 450.73 39.5, 474.67 13.79 M366.16 144.72 C400.27 105.26, 433.48 67.04, 475.72 18.68 M366.16 144.72 C393.53 111.8, 421.43 78.48, 475.72 18.68 M371.14 145.08 C393.65 118.74, 414.53 93.08, 477.43 22.82 M371.14 145.08 C402.07 111.49, 430.19 77.53, 477.43 22.82 M376.79 144.68 C398.96 116.96, 423.02 86.8, 479.13 26.95 M376.79 144.68 C405.71 112.24, 431.87 81.46, 479.13 26.95 M381.77 145.04 C402.53 120.66, 424.65 98.55, 479.53 32.59 M381.77 145.04 C415.6 104.73, 447.91 66.04, 479.53 32.59 M387.42 144.65 C421.09 106.74, 452.24 68, 479.27 38.99 M387.42 144.65 C417.09 111.82, 445.22 80.57, 479.27 38.99 M392.4 145.01 C416.08 117.03, 443.83 87.54, 479 45.39 M392.4 145.01 C412.54 120.34, 434.35 95.05, 479 45.39 M397.39 145.37 C418.8 119.7, 440.1 98.63, 479.4 51.03 M397.39 145.37 C425.95 112.73, 456.43 79.45, 479.4 51.03 M403.03 144.98 C425.28 120.65, 446.29 95.66, 479.14 57.43 M403.03 144.98 C425.92 117.76, 450.02 91.73, 479.14 57.43 M408.02 145.34 C425.07 128.2, 439.73 110.85, 479.53 63.07 M408.02 145.34 C430.26 120.38, 450.08 96.27, 479.53 63.07 M413.66 144.94 C435.93 123.3, 454.67 96.07, 479.27 69.47 M413.66 144.94 C439.06 116.88, 463.51 87.09, 479.27 69.47 M418.65 145.3 C434.28 127.3, 448 113.58, 479.66 75.11 M418.65 145.3 C439.53 121.35, 457.99 99.06, 479.66 75.11 M424.29 144.91 C438.64 130.23, 450.19 116.95, 479.4 81.51 M424.29 144.91 C446.16 119.97, 466.98 96.04, 479.4 81.51 M429.28 145.27 C449.48 121.21, 467.73 98.87, 479.8 87.15 M429.28 145.27 C445.11 126.77, 460.67 109.04, 479.8 87.15 M434.92 144.87 C451.5 125.37, 470.64 107.76, 479.54 93.55 M434.92 144.87 C450.33 126.77, 466.57 106.69, 479.54 93.55 M439.91 145.23 C452.13 132.68, 464.37 120.59, 479.93 99.19 M439.91 145.23 C451.04 133.55, 462.09 121.11, 479.93 99.19 M445.55 144.84 C458.79 129.03, 473.86 114.16, 479.67 105.59 M445.55 144.84 C454.8 134.14, 463.69 123.97, 479.67 105.59 M451.2 144.44 C462.63 132.85, 473.14 119.83, 478.1 113.5 M451.2 144.44 C458.17 135.43, 463.94 127.69, 478.1 113.5 M457.5 143.29 C463.11 139.03, 465.29 134.84, 476.52 121.41 M457.5 143.29 C464 136.22, 469.37 130.32, 476.52 121.41 M465.11 140.63 C467.6 137.97, 471.38 132.46, 476.26 127.8 M465.11 140.63 C467.66 137.17, 471.15 133.88, 476.26 127.8" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C142.9 2.12, 251.08 2.36, 445 0 M32 0 C115.5 -0.6, 199.1 -1.14, 445 0 M445 0 C467.37 0.9, 475.89 9.1, 477 32 M445 0 C464.69 1.75, 476.83 12.79, 477 32 M477 32 C475.75 51.18, 476.7 69.4, 477 113 M477 32 C477.02 52.65, 476.92 71.83, 477 113 M477 113 C475.11 133.14, 465.24 146.78, 445 145 M477 113 C478.86 132.08, 465.62 144.87, 445 145 M445 145 C290.97 146.31, 137.09 146.13, 32 145 M445 145 C337.93 143.89, 230.99 144.81, 32 145 M32 145 C9.61 145.84, -0.8 134.99, 0 113 M32 145 C11.35 145.42, 1.86 135.37, 0 113 M0 113 C1.15 86.4, 0.56 57.51, 0 32 M0 113 C-0.77 88.92, -1.37 66.73, 0 32 M0 32 C1.21 9.29, 10.51 -1.67, 32 0 M0 32 C2.23 9.98, 9.94 -0.68, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(815.1484375 399.59375) rotate(0 220.3125 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> directory:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> name: 22222222222222222222222222222222-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <directory-with-keep-digest></text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 1</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โฆ</text></g><g stroke-linecap="round"><g transform="translate(935.83984375 297.9609375) rotate(0 -127.17742444525584 -6.3835650656164376)"><path d="M-0.07 0.59 C-42.41 -1.51, -211.36 -11.21, -253.47 -13.35 M-1.57 -0.15 C-44.1 -1.99, -212.39 -9.54, -254.28 -11.88" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(935.83984375 297.9609375) rotate(0 -127.17742444525584 -6.3835650656164376)"><path d="M-224.64 -20.31 C-235.83 -15.98, -244.05 -15.65, -255.88 -12.33 M-225.16 -19.94 C-234.34 -18.84, -244.43 -15.58, -254.5 -12.11" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(935.83984375 297.9609375) rotate(0 -127.17742444525584 -6.3835650656164376)"><path d="M-225.65 0.18 C-236.36 -2.61, -244.23 -9.41, -255.88 -12.33 M-226.18 0.55 C-234.92 -4.92, -244.69 -8.23, -254.5 -12.11" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(934.54296875 464.29296875) rotate(0 -122.96248760393826 -133.71583716869355)"><path d="M-0.84 -0.49 C-41.65 -45.05, -205 -222.75, -245.8 -266.94 M0.91 -1.8 C-39.98 -46.25, -205.61 -221.44, -246.84 -265.75" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(934.54296875 464.29296875) rotate(0 -122.96248760393826 -133.71583716869355)"><path d="M-220.88 -252.52 C-227.08 -255.42, -230.79 -256.62, -246.15 -266.29 M-219.26 -252.47 C-226.67 -255.03, -232.47 -258.86, -246.62 -266.61" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(934.54296875 464.29296875) rotate(0 -122.96248760393826 -133.71583716869355)"><path d="M-235.85 -238.47 C-238.83 -244.34, -239.26 -248.61, -246.15 -266.29 M-234.23 -238.42 C-238.04 -244.21, -240.25 -251.41, -246.62 -266.61" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round" transform="translate(808.1090530561523 569.6183182417908) rotate(0 238.5 82)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C6.81 13.38, 14.57 7.61, 18.1 2.48 M3.66 19.09 C7.86 14.77, 10.94 9.38, 18.1 2.48 M2.75 26.24 C8.42 17.74, 14.61 10.75, 24.4 1.33 M2.75 26.24 C7.35 20.79, 11.06 16.63, 24.4 1.33 M2.48 32.64 C8.41 23.44, 17.32 15.14, 30.7 0.18 M2.48 32.64 C8.74 25.77, 13.03 20.51, 30.7 0.18 M2.88 38.28 C8.15 29.11, 19.49 21.57, 34.37 2.05 M2.88 38.28 C13.52 25.23, 25.55 13.16, 34.37 2.05 M2.62 44.68 C16 33.11, 26.07 16.01, 39.36 2.41 M2.62 44.68 C14.25 32.19, 24.76 18.26, 39.36 2.41 M2.36 51.07 C18.74 34.45, 31.43 18.56, 45 2.02 M2.36 51.07 C17.04 34.98, 31.27 17.88, 45 2.02 M2.75 56.72 C12.45 43.26, 26.24 32.38, 49.99 2.38 M2.75 56.72 C13.08 44.38, 23.25 35.02, 49.99 2.38 M2.49 63.11 C13.22 50.48, 25.3 38, 55.63 1.98 M2.49 63.11 C20.59 44.59, 36.16 24.18, 55.63 1.98 M2.88 68.76 C22.9 48.96, 39.83 27.4, 60.62 2.34 M2.88 68.76 C24.33 43.11, 46.62 19.91, 60.62 2.34 M2.62 75.16 C24.58 48.11, 49.17 22.73, 66.26 1.95 M2.62 75.16 C17.85 56.46, 36.34 37, 66.26 1.95 M2.36 81.55 C22.67 56.14, 44.58 32.75, 71.25 2.31 M2.36 81.55 C28.06 52.26, 52.4 23.27, 71.25 2.31 M2.76 87.2 C23.08 61.22, 45.46 37.01, 76.89 1.91 M2.76 87.2 C19.7 68.52, 35.9 49.32, 76.89 1.91 M2.49 93.59 C20.89 69.28, 40.59 49.24, 81.88 2.27 M2.49 93.59 C22.48 70.61, 42.67 48.73, 81.88 2.27 M2.89 99.24 C34.82 63.55, 63.23 31.57, 87.52 1.88 M2.89 99.24 C20.63 78.39, 39.49 56.84, 87.52 1.88 M2.63 105.64 C35.22 68.03, 69.69 27.76, 92.51 2.24 M2.63 105.64 C33.18 70.17, 62.24 35.99, 92.51 2.24 M2.37 112.03 C26.3 85.12, 50.21 57.23, 98.15 1.85 M2.37 112.03 C36.74 71.78, 71.12 32.15, 98.15 1.85 M2.76 117.68 C37.79 74.87, 77.39 32.27, 103.14 2.21 M2.76 117.68 C28.95 87.13, 55.3 54.97, 103.14 2.21 M2.5 124.07 C29.71 93.79, 56.39 67.33, 108.78 1.81 M2.5 124.07 C39.16 80.77, 76.22 37.93, 108.78 1.81 M2.89 129.72 C46.1 83.46, 86.14 32.26, 113.77 2.17 M2.89 129.72 C38.33 90.23, 73.3 50.35, 113.77 2.17 M2.63 136.11 C32.42 101.51, 64.36 66.19, 119.41 1.78 M2.63 136.11 C42.9 88.38, 84.51 40.74, 119.41 1.78 M3.03 141.76 C47.01 92.79, 88.14 44.94, 124.4 2.14 M3.03 141.76 C36.31 103.57, 71.35 65.73, 124.4 2.14 M4.73 145.89 C51.61 90.8, 99.17 38.12, 130.04 1.74 M4.73 145.89 C47.88 98.67, 90.06 49.91, 130.04 1.74 M5.13 151.53 C50.72 98.34, 95.33 50.22, 135.03 2.1 M5.13 151.53 C54.14 93.61, 105.46 35.23, 135.03 2.1 M7.49 154.91 C46.02 110.99, 81.47 70.85, 140.67 1.71 M7.49 154.91 C52.42 103.68, 96.67 51.91, 140.67 1.71 M10.51 157.54 C60.56 98.74, 112.31 43.05, 145.66 2.07 M10.51 157.54 C41.53 122.75, 73.01 84.79, 145.66 2.07 M13.53 160.16 C39.75 129.1, 69.49 95.86, 150.65 2.43 M13.53 160.16 C66.46 100.75, 117.42 41, 150.65 2.43 M17.2 162.03 C70.82 99.54, 124.51 39.14, 156.29 2.03 M17.2 162.03 C45.6 127.78, 75.59 94.57, 156.29 2.03 M20.88 163.9 C56.65 126.92, 91.29 87.42, 161.28 2.39 M20.88 163.9 C71.96 105.59, 125.36 44.09, 161.28 2.39 M25.21 165.02 C65.92 117.85, 106.95 72.45, 166.92 2 M25.21 165.02 C60.62 122.61, 96.68 82.03, 166.92 2 M30.2 165.38 C61.83 128.51, 95.39 92.16, 171.91 2.36 M30.2 165.38 C61.34 132.24, 90.7 98.68, 171.91 2.36 M34.53 166.49 C75.36 119.62, 116.91 72.06, 177.55 1.96 M34.53 166.49 C82.24 112.82, 128.51 59.11, 177.55 1.96 M40.17 166.1 C71.9 131.05, 100.75 95.32, 182.54 2.32 M40.17 166.1 C75.59 123.88, 112.97 81.96, 182.54 2.32 M45.16 166.46 C101.91 101.55, 158.7 35.39, 188.18 1.93 M45.16 166.46 C92.93 108.21, 142.16 51.42, 188.18 1.93 M50.8 166.06 C83.66 126.11, 118.37 86.9, 193.17 2.29 M50.8 166.06 C90.57 121.95, 130.52 76.87, 193.17 2.29 M55.79 166.42 C98.61 114.56, 145.49 65.61, 198.81 1.9 M55.79 166.42 C89.92 128.89, 122.15 90.21, 198.81 1.9 M61.43 166.03 C108.14 112.25, 154.28 57.71, 203.8 2.26 M61.43 166.03 C102.94 116.29, 146.47 66.47, 203.8 2.26 M66.42 166.39 C110.46 115.14, 155.39 64.89, 209.44 1.86 M66.42 166.39 C103.96 123.93, 141.14 82.62, 209.44 1.86 M72.06 165.99 C116.55 115.54, 157.91 65.86, 214.43 2.22 M72.06 165.99 C122.43 107.95, 174.9 47.56, 214.43 2.22 M77.05 166.35 C130.59 105.53, 183.67 43.12, 220.07 1.83 M77.05 166.35 C125.26 111.87, 172.24 58.31, 220.07 1.83 M82.69 165.96 C137.84 105.94, 191.52 41.79, 225.06 2.19 M82.69 165.96 C133.74 107.43, 183.36 50.36, 225.06 2.19 M87.68 166.32 C128.09 119.78, 167.53 73.55, 230.7 1.79 M87.68 166.32 C125.69 123.08, 162.79 79.78, 230.7 1.79 M92.67 166.68 C142.83 109.77, 193.44 51.39, 235.69 2.15 M92.67 166.68 C144.44 108.24, 195.97 47.04, 235.69 2.15 M98.31 166.28 C138.71 115.99, 180.28 69.93, 241.33 1.76 M98.31 166.28 C147.53 110.62, 196.35 53.63, 241.33 1.76 M103.3 166.64 C137.9 125.59, 173.89 87.87, 246.32 2.12 M103.3 166.64 C156.08 106.69, 208.6 46.04, 246.32 2.12 M108.94 166.25 C148.42 122.93, 186.31 81.16, 251.96 1.72 M108.94 166.25 C161 105.15, 212.81 45.73, 251.96 1.72 M113.93 166.61 C141.79 133.21, 172.21 98.82, 256.95 2.08 M113.93 166.61 C156.91 116.88, 202.51 64.75, 256.95 2.08 M119.57 166.21 C168.54 109.97, 219.75 52.97, 262.59 1.69 M119.57 166.21 C167.8 108.87, 219.02 51.26, 262.59 1.69 M124.56 166.57 C161.75 123.88, 199.53 80.79, 267.58 2.05 M124.56 166.57 C156.23 128.01, 190.3 88.85, 267.58 2.05 M130.2 166.18 C169.64 122.3, 211.11 75.14, 272.56 2.41 M130.2 166.18 C168.9 120.31, 208.25 76.02, 272.56 2.41 M135.19 166.54 C184.84 111.09, 234.19 52.53, 278.21 2.01 M135.19 166.54 C187.36 108.27, 238.69 50.75, 278.21 2.01 M140.83 166.15 C183.77 117.65, 225.92 69.25, 283.19 2.37 M140.83 166.15 C187.53 113.68, 233.77 60.45, 283.19 2.37 M145.82 166.51 C182.78 122.72, 219.21 80.97, 288.84 1.98 M145.82 166.51 C200.67 105.62, 254.4 42.05, 288.84 1.98 M151.46 166.11 C182.26 131.38, 212.83 96.05, 293.82 2.34 M151.46 166.11 C186.45 126.29, 219.87 88.44, 293.82 2.34 M156.45 166.47 C204.97 108.71, 255.45 52.85, 299.47 1.94 M156.45 166.47 C211.25 105.36, 265.8 42.21, 299.47 1.94 M162.09 166.08 C213.47 106.17, 266.91 43.59, 304.45 2.3 M162.09 166.08 C216.39 105.62, 269.56 44.48, 304.45 2.3 M167.08 166.44 C219.65 106.64, 271.52 49.11, 310.1 1.91 M167.08 166.44 C214.13 111.77, 261.4 58.06, 310.1 1.91 M172.72 166.04 C208.84 124.53, 247 83.41, 315.08 2.27 M172.72 166.04 C205.15 129.49, 236.42 90.2, 315.08 2.27 M177.71 166.4 C209.21 130.02, 243.57 90.18, 320.73 1.88 M177.71 166.4 C231.87 105.57, 287.31 42.39, 320.73 1.88 M183.35 166.01 C220.85 120.46, 259.07 75.83, 325.71 2.24 M183.35 166.01 C230.79 111.91, 277 57.33, 325.71 2.24 M188.34 166.37 C233.45 112.01, 280.56 61.66, 331.36 1.84 M188.34 166.37 C240.12 108.18, 290.83 49.09, 331.36 1.84 M193.98 165.97 C226.25 131.55, 256.45 92.17, 336.34 2.2 M193.98 165.97 C244.44 109.17, 294.38 52.76, 336.34 2.2 M198.97 166.33 C247.81 112.21, 292.15 60.33, 341.99 1.81 M198.97 166.33 C239.08 119.2, 279.09 71.05, 341.99 1.81 M203.95 166.69 C258.46 101.41, 315.11 40.21, 346.97 2.17 M203.95 166.69 C257.24 106.78, 308.75 46.24, 346.97 2.17 M209.6 166.3 C264.45 102.42, 321.03 37.17, 352.62 1.77 M209.6 166.3 C247.96 124.81, 283.55 84.13, 352.62 1.77 M214.58 166.66 C251.99 125.65, 288.37 82.49, 357.6 2.13 M214.58 166.66 C245.51 132.68, 277.5 95.94, 357.6 2.13 M220.23 166.26 C250.91 130.54, 281.47 95.54, 363.25 1.74 M220.23 166.26 C270.46 109.08, 319.88 50.52, 363.25 1.74 M225.21 166.62 C276.61 104.17, 331.06 44.88, 368.23 2.1 M225.21 166.62 C275.98 109.32, 326.13 50.94, 368.23 2.1 M230.86 166.23 C272.47 114.76, 315.56 64.33, 373.88 1.7 M230.86 166.23 C278.29 112.46, 325.09 57.98, 373.88 1.7 M235.84 166.59 C282.63 113.09, 329.86 58.03, 378.86 2.06 M235.84 166.59 C289.28 107.3, 342.41 45.74, 378.86 2.06 M241.49 166.2 C289.5 107.98, 340.13 49.49, 383.85 2.42 M241.49 166.2 C291 108.5, 340.73 52, 383.85 2.42 M246.47 166.56 C299.38 107.83, 352.11 46.7, 389.49 2.03 M246.47 166.56 C290.21 118.16, 331.91 67.65, 389.49 2.03 M252.12 166.16 C287.75 124.26, 324.97 81.73, 394.48 2.39 M252.12 166.16 C285.46 128.24, 318.01 91.66, 394.48 2.39 M257.1 166.52 C303.62 111.42, 349.89 60.83, 400.12 1.99 M257.1 166.52 C297.83 119.09, 339.11 70.77, 400.12 1.99 M262.75 166.13 C302.79 119.22, 343.84 71.97, 405.11 2.35 M262.75 166.13 C318.14 102.81, 374.01 39.26, 405.11 2.35 M267.73 166.49 C316.44 106.41, 370.98 48.7, 410.75 1.96 M267.73 166.49 C314.68 110.39, 365.02 54.21, 410.75 1.96 M273.38 166.09 C319.98 112.15, 368.66 57.51, 415.74 2.32 M273.38 166.09 C318.87 114.27, 364 62.75, 415.74 2.32 M278.36 166.45 C334.32 101.65, 390.55 37.39, 421.38 1.93 M278.36 166.45 C333.03 105.94, 385.98 44.74, 421.38 1.93 M284.01 166.06 C317.55 130.16, 348.17 93.82, 426.37 2.29 M284.01 166.06 C326.17 119.01, 367.32 70.57, 426.37 2.29 M288.99 166.42 C322.48 126.3, 360.56 85.92, 432.01 1.89 M288.99 166.42 C335.34 112.09, 384.1 55.77, 432.01 1.89 M294.64 166.02 C329.61 122.3, 366.47 79.8, 437 2.25 M294.64 166.02 C335.35 119.83, 373.64 74.32, 437 2.25 M299.62 166.38 C347.99 112.19, 397.82 52.47, 442.64 1.86 M299.62 166.38 C346.57 111.86, 394.7 58.56, 442.64 1.86 M305.27 165.99 C340.79 125.39, 378.2 81.4, 447.63 2.22 M305.27 165.99 C336.56 132.13, 367.18 97.05, 447.63 2.22 M310.25 166.35 C364.09 102.48, 420.3 41.58, 452.62 2.58 M310.25 166.35 C350.18 121.57, 389.7 76.26, 452.62 2.58 M315.9 165.95 C355.72 120.42, 398.24 73.94, 456.95 3.69 M315.9 165.95 C361.7 110.74, 408.98 56.77, 456.95 3.69 M320.88 166.31 C359.3 121.21, 397.93 79.49, 461.94 4.05 M320.88 166.31 C351.82 129.41, 382.91 93.48, 461.94 4.05 M325.87 166.67 C377.05 109.95, 427.19 53.77, 464.96 6.68 M325.87 166.67 C359.4 128.68, 392.04 90.92, 464.96 6.68 M331.51 166.28 C388.57 102.78, 442.05 38.29, 469.29 7.79 M331.51 166.28 C365.47 127.01, 402.68 86.23, 469.29 7.79 M336.5 166.64 C378.68 118.85, 420.7 69.51, 471.65 11.17 M336.5 166.64 C367.77 133.94, 395.41 99.18, 471.65 11.17 M342.14 166.25 C389.11 113.09, 435.96 59.54, 474.67 13.79 M342.14 166.25 C370.01 136.08, 397.01 102.54, 474.67 13.79 M347.13 166.61 C393.05 113.37, 435.84 64.96, 475.72 18.68 M347.13 166.61 C393.9 112.62, 438.68 61.59, 475.72 18.68 M352.77 166.21 C380.68 136.97, 404.58 109.35, 477.43 22.82 M352.77 166.21 C393.66 118.4, 432.23 72.18, 477.43 22.82 M357.76 166.57 C393.89 123.75, 430.73 84.47, 479.13 26.95 M357.76 166.57 C407.4 110.77, 454.79 55.34, 479.13 26.95 M363.4 166.18 C388.23 138.89, 413.94 108.18, 479.53 32.59 M363.4 166.18 C409.58 114.51, 453.68 63.43, 479.53 32.59 M368.39 166.54 C408.72 121.43, 446.29 77.48, 479.27 38.99 M368.39 166.54 C401.91 127.96, 434.91 89.62, 479.27 38.99 M374.03 166.14 C412.5 122.98, 449.36 75.75, 479 45.39 M374.03 166.14 C396.44 139.53, 422.02 110.62, 479 45.39 M379.02 166.5 C409.99 128.35, 442.35 96.32, 479.4 51.03 M379.02 166.5 C406.86 135.7, 432.68 106.1, 479.4 51.03 M384.66 166.11 C402.24 145.5, 422.81 122.74, 479.14 57.43 M384.66 166.11 C422.18 124.54, 458.93 82.58, 479.14 57.43 M389.65 166.47 C419.28 131.51, 452.05 96.59, 479.53 63.07 M389.65 166.47 C419.89 132.79, 448.41 99.82, 479.53 63.07 M395.29 166.07 C426.82 132.48, 455.45 96.46, 479.27 69.47 M395.29 166.07 C416.92 140.62, 438.2 116.72, 479.27 69.47 M400.28 166.43 C420.47 143.05, 442.61 120.52, 479.66 75.11 M400.28 166.43 C426.13 136.5, 452.84 104.75, 479.66 75.11 M405.92 166.04 C427.91 140.97, 448.1 117.36, 479.4 81.51 M405.92 166.04 C431.73 136.16, 458.33 107.47, 479.4 81.51 M410.91 166.4 C439.08 133.08, 467.49 104.88, 479.8 87.15 M410.91 166.4 C439.78 134.31, 465.58 102.73, 479.8 87.15 M416.55 166 C435.71 144.16, 456.89 119.23, 479.54 93.55 M416.55 166 C429.32 151.02, 443.37 135.34, 479.54 93.55 M421.54 166.36 C441.2 143.55, 460.71 119.18, 479.28 99.95 M421.54 166.36 C436.83 150.09, 450.82 132.4, 479.28 99.95 M427.18 165.97 C444.74 144.68, 466.03 122.82, 479.67 105.59 M427.18 165.97 C445.95 144.28, 463.23 123.49, 479.67 105.59 M432.17 166.33 C443.24 153.78, 457.62 140.63, 479.41 111.99 M432.17 166.33 C444.12 153.05, 454.84 140.71, 479.41 111.99 M437.16 166.69 C445.39 157.04, 457.46 143.05, 479.8 117.63 M437.16 166.69 C448.39 153.72, 458.12 143.15, 479.8 117.63 M442.8 166.29 C452.3 154.17, 462.42 144.1, 479.54 124.03 M442.8 166.29 C455.94 150.05, 469.26 134.36, 479.54 124.03 M447.79 166.65 C456.94 155.72, 466.27 142.82, 477.31 132.69 M447.79 166.65 C458.81 155.08, 470.45 141.43, 477.31 132.69 M454.09 165.51 C459.1 157.63, 466.97 149.2, 476.39 139.85 M454.09 165.51 C460.38 158.66, 465.45 152.71, 476.39 139.85 M464.32 159.83 C469.65 156.05, 473.41 148.37, 475.48 147 M464.32 159.83 C466.17 157.25, 468.66 154.88, 475.48 147" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C179.89 -1.74, 328.07 0.08, 445 0 M32 0 C192.02 0.21, 350.62 -0.17, 445 0 M445 0 C465.52 0.25, 477.14 11.05, 477 32 M445 0 C467.31 -0.47, 475.37 11.81, 477 32 M477 32 C475.83 66.77, 477.32 98.5, 477 132 M477 32 C477.31 66.84, 477.55 102.41, 477 132 M477 132 C477.4 152.02, 464.97 162.99, 445 164 M477 132 C478.29 151.95, 467.37 166.29, 445 164 M445 164 C309.65 164.67, 175.86 163.51, 32 164 M445 164 C339.17 163.73, 232.87 164.46, 32 164 M32 164 C9.7 164.07, -1.59 152.38, 0 132 M32 164 C9.56 164.33, 1.77 155.2, 0 132 M0 132 C1.13 92.98, -0.18 57.12, 0 32 M0 132 C0.33 111.2, 1.53 90.02, 0 32 M0 32 C-1.62 11.04, 10.67 1.54, 32 0 M0 32 C0.37 11.97, 12.23 -2.15, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(813.1090530561523 574.6183182417908) rotate(0 220.3125 76.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> file:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> name: 33333333333333333333333333333333-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <blob-a-digest></text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 3</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> executable: false</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="134.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โฆ</text></g><g stroke-linecap="round"><g transform="translate(942.4901398439579 641.0079592022902) rotate(0 -391.38489601802087 -282.46266052120785)"><path d="M-0.12 0.36 C-130.24 -93.43, -651.31 -469.22, -781.67 -563.32 M-1.64 -0.49 C-131.84 -94.62, -652.36 -471.5, -782.65 -565.29" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(942.4901398439579 641.0079592022902) rotate(0 -391.38489601802087 -282.46266052120785)"><path d="M-753.18 -557.22 C-759.58 -560.05, -767.44 -560.17, -782.06 -566.37 M-754.28 -557.06 C-760.35 -558.69, -767.09 -560.3, -781.67 -564.75" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(942.4901398439579 641.0079592022902) rotate(0 -391.38489601802087 -282.46266052120785)"><path d="M-765.2 -540.58 C-768.89 -547.05, -774.11 -550.81, -782.06 -566.37 M-766.3 -540.42 C-769.51 -545.96, -773.48 -551.41, -781.67 -564.75" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask></svg> \ No newline at end of file diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/tvixbolt.webm b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvixbolt.webm new file mode 100644 index 000000000000..69bd20f193cb --- /dev/null +++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvixbolt.webm Binary files differdiff --git a/users/flokli/presentations/2023-09-13-asg-tvix-store/default.nix b/users/flokli/presentations/2023-09-13-asg-tvix-store/default.nix new file mode 100644 index 000000000000..840f21de8103 --- /dev/null +++ b/users/flokli/presentations/2023-09-13-asg-tvix-store/default.nix @@ -0,0 +1,32 @@ +{ depot, pkgs, ... }: + +let + inherit (pkgs) + fontconfig qrencode runCommand stdenv; + mkQr = url: runCommand "qrcode.png" { } '' + ${qrencode}/bin/qrencode -o $out -t SVG -s 5 \ + --background=fafafa \ + --foreground=000000 \ + ${url} + ''; +in +stdenv.mkDerivation { + name = "2023-asg-tvix-store"; + src = ./.; + + FONTCONFIG_FILE = pkgs.makeFontsConf { + fontDirectories = with pkgs; [ jetbrains-mono fira fira-code fira-mono lato ]; + }; + + nativeBuildInputs = [ pkgs.reveal-md pkgs.graphviz ]; + + buildPhase = '' + cp ${depot.tvix.logo}/logo.png tvix-logo.png + cp ${mkQr "https://flokli.de"} qrcode-flokli.svg + cp ${mkQr "https://tvix.dev"} qrcode-tvix.svg + + mkdir -p $out + cp tvix-store-graph-blob-directory.svg $out/ + reveal-md --static $out presentation.md + ''; +} diff --git a/users/flokli/presentations/2023-09-13-asg-tvix-store/presentation.md b/users/flokli/presentations/2023-09-13-asg-tvix-store/presentation.md new file mode 100644 index 000000000000..978934f9a48d --- /dev/null +++ b/users/flokli/presentations/2023-09-13-asg-tvix-store/presentation.md @@ -0,0 +1,138 @@ +--- +author: +- Florian Klink +date: 2023-09-09 +title: "tvix-store: A content-addressed file system and sync protocol" +theme: moon +revealOptions: + transition: 'fade' +--- + +## tvix-store +### A content-addressed file system and sync protocol + +2023-09-13 + +Florian Klink / flokli + +--- + +## Whoami + +- <!-- .element: class="fragment" --> + flokli +- <!-- .element: class="fragment" --> + Nix/NixOS contributor + - maintain systemd, nss and more low-level stuff there +- <!-- .element: class="fragment" --> + Freelance Nix/DevOps consultant + +Note: more Kubernetes/DevOps exposure with work + +--- + +## What is tvix-store? +- <!-- .element: class="fragment" --> + A new implementation of a content-addressed "storage system" + - <!-- .element: class="fragment" --> + part of the Tvix Project, a (WIP) reimplementation of Nix and auxillary components in Rust + - <!-- .element: class="fragment" --> + Storage model: think about git trees and its Merkle DAGโฆ + - <!-- .element: class="fragment" --> + โฆ but with nicer wire format (`.proto`) and hash function (blake3) + +--- + +## Storage model +- <!-- .element: class="fragment" --> + Once you know the root: everything else is content-addressed + - <!-- .element: class="fragment" --> + No timestamps, no uid/gid, no xattrs, only one way to represent the same tree +- <!-- .element: class="fragment" --> + Automatic dedup of identical subtrees in different file system trees +- <!-- .element: class="fragment" --> + Automatic dedup of identical blobs (and you can do more chunking underneath too) + +--- + +## Storage model (cont.) +- <!-- .element: class="fragment" --> + Granular seekable access into blobs +- <!-- .element: class="fragment" --> + verified streaming thanks to BLAKE3 and Bao, faulty data is detected early on +- <!-- .element: class="fragment" --> + Everything below can be retrieved from anyone without having to trust (P2P substitution, CDNs, โฆ) + +--- + +## Usecases +- <!-- .element: class="fragment" --> + File system tree delivery +- <!-- .element: class="fragment" --> + Container image delivery +- <!-- .element: class="fragment" --> + Backing store for VCS +- <!-- .element: class="fragment" --> + Granular access into large datasets + +--- + +## Status +- <!-- .element: class="fragment" --> + In-memory backend, a local K/V backend (Sled) +- <!-- .element: class="fragment" --> + FUSE filesystem +- <!-- .element: class="fragment" --> + A gRPC API to transfer things, bindings for golang and rust +- <!-- .element: class="fragment" --> + some object storage backends in development (GCS, NATS) +- <!-- .element: class="fragment" --> + FUTUREWORK: more storage backends / store composition / in-kernel module? + +Notes: of course you can use your own network protocol too, like HTTP CAS or iroh....plug different stores together to represent caches, blobfs + +--- + +## Contributing + +- <!-- .element: class="fragment" --> + Join the IRC channel (`#tvl` on `hackint`), bridged to Matrix and XMPP +- <!-- .element: class="fragment" --> + Check our issue tracker +- <!-- .element: class="fragment" --> + Try to use it and tell us how you broke it! + +Note: if this sounds useful to you, reach out! + +--- + +# Thanks! + +<style> +.container{ + display: flex; +} +.col{ + flex: 1; +} +</style> + +<div class="container"> + +<div class="col"> +Florian Klink / <a href="https://flokli.de">flokli.de</a><br /> +<img src="qrcode-flokli.svg" /> +</div> + +<div class="col"> +Tvix / <a href="https://tvix.dev">tvix.dev</a><br /> +<img src="qrcode-tvix.svg" /> +</div> + +</div> + +--- + +## Structure + +[tvix-store graph](tvix-store-graph-blob-directory.svg) diff --git a/users/flokli/presentations/2023-09-13-asg-tvix-store/tvix-store-graph-blob-directory.svg b/users/flokli/presentations/2023-09-13-asg-tvix-store/tvix-store-graph-blob-directory.svg new file mode 100644 index 000000000000..2c87350d5b79 --- /dev/null +++ b/users/flokli/presentations/2023-09-13-asg-tvix-store/tvix-store-graph-blob-directory.svg @@ -0,0 +1,17 @@ +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 691.01171875 836.19140625" width="691.01171875" height="836.19140625"> + <!-- svg-source:excalidraw --> + + <defs> + <style class="style-fonts"> + @font-face { + font-family: "Virgil"; + src: url("https://excalidraw.com/Virgil.woff2"); + } + @font-face { + font-family: "Cascadia"; + src: url("https://excalidraw.com/Cascadia.woff2"); + } + </style> + + </defs> + <rect x="0" y="0" width="691.01171875" height="836.19140625" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 64.05078125) rotate(0 71.10546875 23.685546875)"><path d="M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M1.58 10.65 C3.55 6.56, 7.08 5.83, 10.11 0.84 M1.58 10.65 C4.54 7.52, 5.83 5.33, 10.11 0.84 M1.97 16.29 C5.97 12.52, 7.97 9.89, 14.44 1.95 M1.97 16.29 C5.39 12.84, 8.96 8.01, 14.44 1.95 M1.71 22.69 C5.44 18.07, 11.5 12.49, 19.43 2.31 M1.71 22.69 C6.47 16.78, 11.69 12.31, 19.43 2.31 M1.45 29.09 C9.97 18.06, 21.19 8.77, 25.07 1.92 M1.45 29.09 C9.61 17.76, 19.35 7.56, 25.07 1.92 M1.85 34.73 C6.66 29.67, 14.83 20.48, 30.06 2.28 M1.85 34.73 C8.29 27.25, 17.05 18.03, 30.06 2.28 M2.24 40.37 C10.77 28.82, 20.92 18.16, 35.7 1.88 M2.24 40.37 C14.65 26.46, 25.75 13.13, 35.7 1.88 M3.29 45.26 C19.71 28.3, 32.79 9.79, 40.69 2.24 M3.29 45.26 C14.19 32.96, 23.82 22.41, 40.69 2.24 M6.97 47.13 C21.76 30.34, 38.17 12.66, 45.67 2.6 M6.97 47.13 C19.37 32.8, 32.58 18.66, 45.67 2.6 M12.61 46.74 C23.7 34.53, 32.71 23.85, 51.32 2.21 M12.61 46.74 C23.04 33.52, 34.64 21.81, 51.32 2.21 M17.6 47.1 C32.92 32.2, 44.82 17.61, 56.3 2.57 M17.6 47.1 C26.86 36.54, 34.43 27.02, 56.3 2.57 M22.58 47.46 C36.55 31.33, 51.38 11.55, 61.95 2.17 M22.58 47.46 C35.33 33.95, 46.66 20.15, 61.95 2.17 M28.23 47.06 C40.8 33.63, 55.34 16.98, 66.93 2.53 M28.23 47.06 C36.54 38.37, 43.25 29.02, 66.93 2.53 M33.21 47.42 C48.21 29.57, 61.74 14.8, 72.58 2.14 M33.21 47.42 C44.03 35.16, 55.8 21.76, 72.58 2.14 M38.86 47.03 C49.09 34.45, 58.15 26.51, 77.56 2.5 M38.86 47.03 C53.73 30.24, 67.18 15.29, 77.56 2.5 M43.84 47.39 C57.75 33, 73.31 14.12, 82.55 2.86 M43.84 47.39 C53.3 37.24, 61.17 27.55, 82.55 2.86 M49.49 46.99 C62.68 35.47, 70.45 21.6, 88.19 2.46 M49.49 46.99 C61.84 34.86, 73.11 21.46, 88.19 2.46 M54.47 47.35 C61.1 38.29, 69.25 27.83, 93.18 2.82 M54.47 47.35 C66.26 32.61, 76.99 20.02, 93.18 2.82 M60.12 46.96 C73.27 30.81, 84.64 16.32, 98.82 2.43 M60.12 46.96 C69.94 34.83, 80.66 21.92, 98.82 2.43 M65.1 47.32 C73.48 35.83, 86.65 26.47, 103.81 2.79 M65.1 47.32 C78.64 32.14, 91.97 18.02, 103.81 2.79 M70.09 47.68 C85.6 30.67, 98.3 12.05, 109.45 2.4 M70.09 47.68 C81.34 34.02, 94.32 21.42, 109.45 2.4 M75.73 47.28 C89.43 31.61, 102.84 18.22, 114.44 2.76 M75.73 47.28 C85.67 35.55, 96.35 22.65, 114.44 2.76 M80.72 47.64 C91.93 34.98, 103.63 17.52, 119.43 3.12 M80.72 47.64 C90.36 36.8, 98.88 24.6, 119.43 3.12 M86.36 47.25 C101.83 29.55, 117.32 12.7, 125.07 2.72 M86.36 47.25 C99.66 32.13, 111.08 17.18, 125.07 2.72 M91.35 47.61 C104.35 33.22, 117.8 19.95, 130.06 3.08 M91.35 47.61 C103.56 34.45, 114.2 20.27, 130.06 3.08 M96.99 47.21 C103.59 38.71, 113.94 28.96, 136.36 1.93 M96.99 47.21 C106.26 36.64, 117.24 24.44, 136.36 1.93 M101.98 47.57 C111.9 34.49, 126.09 20.88, 140.03 3.8 M101.98 47.57 C114.1 33.1, 128.09 16.65, 140.03 3.8 M106.97 47.93 C114.85 39.44, 121.62 28.8, 141.74 7.93 M106.97 47.93 C117.52 37.38, 125.94 26.56, 141.74 7.93 M112.61 47.54 C120.79 38.54, 126.18 28.87, 144.1 11.31 M112.61 47.54 C125.62 33.32, 137.05 19.84, 144.1 11.31 M117.6 47.9 C122.32 42.89, 130.44 32.85, 144.5 16.96 M117.6 47.9 C126.99 36.5, 135.5 27.33, 144.5 16.96 M123.24 47.51 C127.95 43.64, 132.17 36.33, 144.23 23.35 M123.24 47.51 C129.47 41.35, 136.09 32.69, 144.23 23.35 M128.23 47.87 C131.28 40.54, 137.28 36.59, 143.97 29.75 M128.23 47.87 C132.3 43.29, 136.94 37.23, 143.97 29.75 M132.56 48.98 C136.69 46.03, 136.69 42.77, 143.71 36.15 M132.56 48.98 C136.02 46.4, 138.73 42.29, 143.71 36.15" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M11.84 0 M11.84 0 C38.02 2.65, 61.84 0.28, 130.37 0 M11.84 0 C38.21 1.94, 65.76 1.5, 130.37 0 M130.37 0 C139.47 1, 143.75 5.88, 142.21 11.84 M130.37 0 C137.21 0.92, 139.99 4.87, 142.21 11.84 M142.21 11.84 C140.42 17.68, 141.72 25.2, 142.21 35.53 M142.21 11.84 C142.2 20.55, 141.47 28.9, 142.21 35.53 M142.21 35.53 C143.14 42.65, 138.92 45.76, 130.37 47.37 M142.21 35.53 C141.26 45.41, 136.07 48.58, 130.37 47.37 M130.37 47.37 C100.03 46.91, 70.33 46.17, 11.84 47.37 M130.37 47.37 C93.27 45.68, 57.65 45.47, 11.84 47.37 M11.84 47.37 C3.83 47.21, 0.04 43.58, 0 35.53 M11.84 47.37 C3.58 49.37, 2.09 44.62, 0 35.53 M0 35.53 C-0.11 25.56, 1.74 17.16, 0 11.84 M0 35.53 C-0.73 27.14, 0.25 16.41, 0 11.84 M0 11.84 C1.26 4.93, 5.18 0.97, 11.84 0 M0 11.84 C0.62 1.9, 3.42 2.05, 11.84 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(15 78.13632812499998) rotate(0 65.625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x01 0x02 0x03</text></g><g stroke-linecap="round" transform="translate(11.80078125 149.404296875) rotate(0 51.5 24.5)"><path d="M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M1.69 10.74 C4.79 9.22, 6.14 6.4, 10.22 0.93 M1.69 10.74 C4.82 7.44, 8.36 3.46, 10.22 0.93 M1.43 17.14 C5.08 10.94, 12.94 4.63, 14.55 2.05 M1.43 17.14 C7.42 12.34, 11.48 5.83, 14.55 2.05 M1.82 22.78 C7.79 17.76, 9.92 11.78, 20.19 1.65 M1.82 22.78 C6.91 17.54, 10.69 12.89, 20.19 1.65 M1.56 29.18 C10.68 19.58, 16.05 8.86, 25.18 2.01 M1.56 29.18 C6.66 24.1, 10.7 18.88, 25.18 2.01 M1.95 34.82 C14.06 20.7, 22.56 11.87, 30.16 2.37 M1.95 34.82 C12.05 23.95, 21.94 11.12, 30.16 2.37 M1.69 41.22 C7.98 31.97, 17.66 24.23, 35.81 1.98 M1.69 41.22 C14.67 26.87, 25.96 11.6, 35.81 1.98 M3.4 45.35 C12.42 31.5, 21.88 20.01, 40.79 2.34 M3.4 45.35 C14.79 32.81, 25.98 18.39, 40.79 2.34 M5.76 48.73 C19.03 33.54, 30.13 20.75, 46.44 1.94 M5.76 48.73 C15.95 35.19, 26.77 22.13, 46.44 1.94 M10.75 49.09 C25.08 35.85, 34.98 19.45, 51.42 2.3 M10.75 49.09 C25.36 34.29, 38.07 17.14, 51.42 2.3 M14.42 50.96 C25.24 34.64, 40.8 20.23, 57.07 1.91 M14.42 50.96 C31.56 31.52, 47.54 13.07, 57.07 1.91 M20.07 50.57 C36.58 31.78, 51.41 11.57, 62.05 2.27 M20.07 50.57 C35.27 32.37, 49.62 15.57, 62.05 2.27 M25.05 50.93 C33.69 40.39, 45.49 30.76, 67.7 1.87 M25.05 50.93 C39.57 35.58, 52.89 19.97, 67.7 1.87 M30.04 51.29 C42.51 36.46, 56.98 20.61, 72.68 2.23 M30.04 51.29 C39.08 40.34, 49.2 29.9, 72.68 2.23 M35.68 50.89 C47.44 39.9, 56.68 27.08, 78.33 1.84 M35.68 50.89 C52.05 33.69, 66.26 15.86, 78.33 1.84 M40.67 51.25 C53.32 36.02, 69.16 19.85, 83.31 2.2 M40.67 51.25 C53.19 37.93, 64.14 24.78, 83.31 2.2 M46.31 50.86 C62.11 32.31, 78.52 15.58, 88.96 1.8 M46.31 50.86 C57.68 37.06, 69.81 23.8, 88.96 1.8 M51.3 51.22 C59.29 38.98, 72.37 29.04, 93.94 2.16 M51.3 51.22 C64.73 35.51, 79.44 17.72, 93.94 2.16 M56.94 50.83 C69.81 34.76, 86.63 19.59, 98.93 2.52 M56.94 50.83 C72.69 32.66, 90.36 14.37, 98.93 2.52 M61.93 51.19 C77.07 35.49, 89.58 18.3, 101.95 5.15 M61.93 51.19 C76.05 33.49, 92.37 16.74, 101.95 5.15 M67.57 50.79 C76.42 41.59, 82.64 33.63, 103 10.04 M67.57 50.79 C76.25 40.25, 84.99 30.22, 103 10.04 M72.56 51.15 C79.09 44.86, 86.76 36.01, 103.4 15.68 M72.56 51.15 C78.4 43.41, 84.37 36.12, 103.4 15.68 M78.2 50.76 C84.15 41.95, 92.56 33.07, 103.13 22.08 M78.2 50.76 C88.29 39.34, 96.97 28.67, 103.13 22.08 M83.19 51.12 C88.53 45.31, 96.1 36.68, 103.53 27.72 M83.19 51.12 C91.24 41.6, 98.32 33.94, 103.53 27.72 M88.83 50.72 C90.5 47.29, 97.87 43.07, 103.27 34.12 M88.83 50.72 C94.68 44.06, 98.18 39.73, 103.27 34.12 M93.82 51.08 C96.56 47.12, 100.38 44.41, 104.97 38.25 M93.82 51.08 C96.61 47.17, 101.29 42.48, 104.97 38.25" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M12.25 0 M12.25 0 C28.51 0, 42.5 2.25, 90.75 0 M12.25 0 C32.14 -0.62, 52.39 1.04, 90.75 0 M90.75 0 C100.71 0.09, 101.8 3.77, 103 12.25 M90.75 0 C97.64 -0.83, 104 5.94, 103 12.25 M103 12.25 C101.33 19.82, 101.63 32.15, 103 36.75 M103 12.25 C102.35 21.13, 102.15 31.06, 103 36.75 M103 36.75 C104.56 46.87, 99.61 50.21, 90.75 49 M103 36.75 C104.15 46.69, 101.13 47.94, 90.75 49 M90.75 49 C62.94 48.31, 35.39 47.65, 12.25 49 M90.75 49 C67.73 49.76, 43.51 49.69, 12.25 49 M12.25 49 C3.6 47.48, -1.71 45.84, 0 36.75 M12.25 49 C3.19 49.75, -1.85 43.96, 0 36.75 M0 36.75 C-0.6 25.33, 0.5 18.11, 0 12.25 M0 36.75 C-0.07 30.3, -1.01 22.9, 0 12.25 M0 12.25 C-1.34 4.43, 2.32 -0.12, 12.25 0 M0 12.25 C-0.18 4.12, 4.26 -0.37, 12.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(16.80078125 164.30429687499998) rotate(0 42.1875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x04 0x05</text></g><g stroke-linecap="round" transform="translate(14.93359375 236.67578125) rotate(0 4.138671875 25.552734375)"><path d="M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M0.11 8.04 C1.82 6.13, 2.96 4.61, 4.7 2.76 M0.11 8.04 C1.05 6.92, 2.71 5.33, 4.7 2.76 M-0.15 14.44 C2.61 10.36, 4.52 9.27, 9.69 3.12 M-0.15 14.44 C3 10.45, 7.79 5.81, 9.69 3.12 M0.24 20.08 C1.78 17.33, 6.21 13.86, 9.43 9.52 M0.24 20.08 C3.9 16.52, 6.5 12.39, 9.43 9.52 M-0.02 26.48 C2.46 23.23, 7.19 20.21, 9.82 15.16 M-0.02 26.48 C2.52 23.58, 5.92 19.96, 9.82 15.16 M-0.28 32.88 C3.43 29.78, 4.96 26.51, 9.56 21.56 M-0.28 32.88 C1.68 30.37, 4.75 26.47, 9.56 21.56 M0.11 38.52 C2.43 35.55, 6.84 32.06, 9.3 27.96 M0.11 38.52 C3.34 34.69, 7.79 30.27, 9.3 27.96 M-0.15 44.92 C4.41 39.72, 7.29 36.44, 9.69 33.6 M-0.15 44.92 C2.92 41.4, 5.54 37.99, 9.69 33.6 M0.9 49.81 C2.58 47.91, 5.61 43.76, 9.43 40 M0.9 49.81 C3.14 46.67, 5.91 43.78, 9.43 40 M3.92 52.43 C5.01 50.49, 6.18 49.02, 9.82 45.64 M3.92 52.43 C5.89 50.77, 7.52 48.28, 9.82 45.64" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M2.07 0 M2.07 0 C2.97 -0.21, 4.11 0.12, 6.21 0 M2.07 0 C3.19 -0.16, 4.23 0.11, 6.21 0 M6.21 0 C8.97 0.45, 9.01 1.39, 8.28 2.07 M6.21 0 C9.57 -1.98, 9.89 2.6, 8.28 2.07 M8.28 2.07 C8.66 12.58, 9.73 22.31, 8.28 49.04 M8.28 2.07 C8.53 15.26, 7.49 28.86, 8.28 49.04 M8.28 49.04 C6.95 49.38, 7.58 52.75, 6.21 51.11 M8.28 49.04 C10.35 52.7, 9.64 53.24, 6.21 51.11 M6.21 51.11 C5.12 51.08, 3.76 50.9, 2.07 51.11 M6.21 51.11 C5.14 51.07, 4.25 51.01, 2.07 51.11 M2.07 51.11 C2.45 52.5, -1.17 52.21, 0 49.04 M2.07 51.11 C-1.47 50.3, 2.23 51.76, 0 49.04 M0 49.04 C-0.13 38.39, -0.48 26.48, 0 2.07 M0 49.04 C-1.31 32.15, -0.35 13.45, 0 2.07 M0 2.07 C0.41 0.36, -0.67 -0.56, 2.07 0 M0 2.07 C-0.35 -0.58, -1.03 -1.54, 2.07 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(10.23046875 10) rotate(0 23.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Blobs</text></g><g transform="translate(279.12890625 12.759374999999977) rotate(0 51.5625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Directories</text></g><g stroke-linecap="round" transform="translate(283.875 68.3828125) rotate(0 197 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.28 14.49, 8.49 11.04, 18.1 2.48 M3.66 19.09 C7.7 14.57, 11.52 11.66, 18.1 2.48 M2.75 26.24 C11.51 19.05, 17.92 11.98, 24.4 1.33 M2.75 26.24 C7.23 20.55, 12.55 15.89, 24.4 1.33 M2.48 32.64 C12.12 24.05, 19.28 13.72, 30.7 0.18 M2.48 32.64 C9.16 24.56, 14.83 17.39, 30.7 0.18 M2.88 38.28 C11.43 28.28, 22.31 16.44, 34.37 2.05 M2.88 38.28 C14.6 24.25, 27.89 9.94, 34.37 2.05 M2.62 44.68 C9.32 34.89, 17.39 26.88, 39.36 2.41 M2.62 44.68 C11.95 33.82, 21.66 21.02, 39.36 2.41 M2.36 51.07 C12.81 41.21, 19.43 27.76, 45 2.02 M2.36 51.07 C16.68 33.29, 30.46 16.46, 45 2.02 M2.75 56.72 C16.64 41.98, 28.98 26.21, 49.99 2.38 M2.75 56.72 C21 35.95, 39.52 14.81, 49.99 2.38 M2.49 63.11 C19.63 44.26, 39.08 21.21, 55.63 1.98 M2.49 63.11 C17.01 46.73, 31.61 30.35, 55.63 1.98 M2.88 68.76 C22.9 46.33, 43.08 21.26, 60.62 2.34 M2.88 68.76 C23.19 45.39, 41.88 24.57, 60.62 2.34 M2.62 75.16 C23.83 54.32, 39.89 29.17, 66.26 1.95 M2.62 75.16 C18.47 56.13, 34.46 38.2, 66.26 1.95 M2.36 81.55 C29.87 50.88, 54.51 22.49, 71.25 2.31 M2.36 81.55 C26.5 54.38, 49.03 26.57, 71.25 2.31 M2.76 87.2 C25.76 61.24, 52.69 29.56, 76.89 1.91 M2.76 87.2 C22.68 64.01, 42.85 40.22, 76.89 1.91 M2.49 93.59 C30.49 61.54, 57.87 26.06, 81.88 2.27 M2.49 93.59 C31.33 60.52, 59.32 27.03, 81.88 2.27 M2.89 99.24 C28.16 70.88, 49.29 44.38, 87.52 1.88 M2.89 99.24 C22.19 77.54, 40.48 55.68, 87.52 1.88 M2.63 105.64 C27.42 78.52, 50.89 50.01, 92.51 2.24 M2.63 105.64 C38.6 64.43, 74.48 23.22, 92.51 2.24 M2.37 112.03 C35.69 71.87, 67.49 34.66, 98.15 1.85 M2.37 112.03 C29.87 80.37, 57.31 48.8, 98.15 1.85 M2.76 117.68 C35.35 78.95, 70.62 41.89, 103.14 2.21 M2.76 117.68 C24.16 94.43, 44.43 69.26, 103.14 2.21 M3.16 123.32 C27.31 95.67, 53.64 64.97, 108.78 1.81 M3.16 123.32 C40.3 79.27, 78.36 37.25, 108.78 1.81 M4.86 127.45 C35.22 90.31, 66.42 52.47, 113.77 2.17 M4.86 127.45 C27.89 100.07, 53.15 74.47, 113.77 2.17 M5.26 133.1 C41.54 89.35, 82.58 48.1, 119.41 1.78 M5.26 133.1 C50.78 80.9, 96.57 28.36, 119.41 1.78 M7.62 136.47 C50.54 85.68, 96.45 32.28, 124.4 2.14 M7.62 136.47 C50.54 87.26, 95.13 37.29, 124.4 2.14 M10.64 139.1 C44.47 96.28, 83.89 53.02, 130.04 1.74 M10.64 139.1 C49.89 96.71, 86.98 53.36, 130.04 1.74 M14.31 140.97 C55.74 92.08, 97.64 42.48, 135.03 2.1 M14.31 140.97 C49.36 97.94, 86.03 55.71, 135.03 2.1 M17.33 143.59 C62.88 90.12, 107.49 38.88, 140.67 1.71 M17.33 143.59 C52.29 102.09, 87.68 60.93, 140.67 1.71 M21.01 145.46 C48.28 114.13, 76.95 81.48, 145.66 2.07 M21.01 145.46 C64.61 95.84, 107.71 47.87, 145.66 2.07 M25.34 146.58 C73.23 89.86, 125.31 35.24, 150.65 2.43 M25.34 146.58 C51.9 117.94, 78.32 86.29, 150.65 2.43 M32.29 144.67 C78.07 95.21, 118.24 44.56, 156.29 2.03 M32.29 144.67 C74.54 98.13, 114.59 49.94, 156.29 2.03 M37.28 145.03 C85.09 88.58, 133.69 36.74, 161.28 2.39 M37.28 145.03 C69.44 108.12, 100.26 71.2, 161.28 2.39 M42.92 144.64 C90.72 91.41, 136.95 40.49, 166.92 2 M42.92 144.64 C66.95 114.58, 93.7 85.42, 166.92 2 M47.91 145 C75.02 114.35, 102.25 80.22, 171.91 2.36 M47.91 145 C87.4 99.39, 126.35 54.03, 171.91 2.36 M52.9 145.36 C79.04 113.6, 107.32 79.81, 177.55 1.96 M52.9 145.36 C77.61 115.94, 103.48 86, 177.55 1.96 M58.54 144.96 C92.12 105.3, 128.8 63.9, 182.54 2.32 M58.54 144.96 C106.61 90.51, 152.9 36.35, 182.54 2.32 M63.53 145.32 C107.47 99.27, 149.03 48.65, 188.18 1.93 M63.53 145.32 C96.13 109.61, 126.87 72.99, 188.18 1.93 M69.17 144.93 C102.47 104.11, 137.86 61.81, 193.17 2.29 M69.17 144.93 C110.04 98.38, 152.23 51.84, 193.17 2.29 M74.16 145.29 C104.78 108.19, 138.63 71.58, 198.81 1.9 M74.16 145.29 C99.31 117.03, 123.84 87.71, 198.81 1.9 M79.8 144.9 C130.71 87.32, 178.54 30.53, 203.8 2.26 M79.8 144.9 C124.29 94.42, 167.51 43.91, 203.8 2.26 M84.79 145.26 C111.41 111.48, 143.37 80.45, 209.44 1.86 M84.79 145.26 C130.36 93.05, 176.97 39.33, 209.44 1.86 M90.43 144.86 C128.08 97.62, 168.49 56.08, 214.43 2.22 M90.43 144.86 C130.35 96.04, 171.08 48.72, 214.43 2.22 M95.42 145.22 C146.19 90.39, 192.4 31.56, 220.07 1.83 M95.42 145.22 C120.72 114.85, 146.9 84.4, 220.07 1.83 M101.06 144.83 C127.22 111.52, 152.8 83.04, 225.06 2.19 M101.06 144.83 C132.86 108.13, 163.34 71.68, 225.06 2.19 M106.05 145.19 C154.17 92.56, 198.8 39.31, 230.7 1.79 M106.05 145.19 C136.53 110.59, 166.08 76.75, 230.7 1.79 M111.69 144.79 C138.65 117.15, 162.06 85.81, 235.69 2.15 M111.69 144.79 C137.61 113.65, 164.22 81.98, 235.69 2.15 M116.68 145.15 C157.06 99.25, 194.58 56.02, 241.33 1.76 M116.68 145.15 C155.1 99.61, 194.87 53.74, 241.33 1.76 M122.32 144.76 C155.38 104.12, 188.9 66.56, 246.32 2.12 M122.32 144.76 C171.19 89.94, 218.89 34.94, 246.32 2.12 M127.31 145.12 C160.63 107.75, 195.41 68.39, 251.96 1.72 M127.31 145.12 C171.79 94.11, 216.13 43.25, 251.96 1.72 M132.95 144.72 C167.14 104.52, 206.3 64.21, 256.95 2.08 M132.95 144.72 C164.99 105.52, 198.48 68.92, 256.95 2.08 M137.94 145.08 C183.71 92.31, 226.45 45.7, 262.59 1.69 M137.94 145.08 C163.89 115.91, 190.22 85.14, 262.59 1.69 M143.58 144.69 C174.92 109.48, 206.89 70.46, 267.58 2.05 M143.58 144.69 C170.4 113.21, 199.74 82.17, 267.58 2.05 M148.57 145.05 C178.08 111.76, 203.33 81.73, 272.56 2.41 M148.57 145.05 C173.64 115.53, 198.59 86.39, 272.56 2.41 M154.21 144.65 C187.55 108.07, 219.55 68.66, 278.21 2.01 M154.21 144.65 C200.06 91.97, 243.47 39.89, 278.21 2.01 M159.2 145.01 C186.66 112.8, 212.53 85.21, 283.19 2.37 M159.2 145.01 C207.12 89.92, 255.48 37.14, 283.19 2.37 M164.19 145.37 C202.12 104.92, 237.2 59.79, 288.84 1.98 M164.19 145.37 C194.82 106.77, 226.84 70.45, 288.84 1.98 M169.83 144.98 C212.12 93.05, 258.35 43.46, 293.82 2.34 M169.83 144.98 C201.44 106.46, 235.81 67.61, 293.82 2.34 M174.82 145.34 C207.54 111.65, 235.58 74.76, 299.47 1.94 M174.82 145.34 C199.92 118.44, 225.68 88.79, 299.47 1.94 M180.46 144.94 C208.5 116.42, 230.68 85.13, 304.45 2.3 M180.46 144.94 C218.97 100.78, 259.51 55.75, 304.45 2.3 M185.45 145.31 C221.24 102.72, 256.86 59.74, 310.1 1.91 M185.45 145.31 C232.8 92.21, 280.33 37.72, 310.1 1.91 M191.09 144.91 C238.27 88.19, 288.2 35.33, 315.08 2.27 M191.09 144.91 C220.59 109.55, 252.22 74.41, 315.08 2.27 M196.08 145.27 C239.76 94.85, 284.75 46.35, 320.73 1.88 M196.08 145.27 C230.49 104.94, 265.25 68.33, 320.73 1.88 M201.72 144.88 C234.66 108.79, 263.22 71.79, 325.71 2.24 M201.72 144.88 C237.32 101.42, 273.7 59.07, 325.71 2.24 M206.71 145.24 C253.21 90.64, 300.99 37.76, 331.36 1.84 M206.71 145.24 C240.65 108.22, 273.71 69.33, 331.36 1.84 M212.35 144.84 C242.19 114.5, 267.67 79.17, 336.34 2.2 M212.35 144.84 C236.6 115.07, 262.16 86.57, 336.34 2.2 M217.34 145.2 C251.25 105.29, 286.42 64.08, 341.99 1.81 M217.34 145.2 C249.04 108.05, 278.75 71.6, 341.99 1.81 M222.98 144.81 C254.78 109.71, 284.95 73.51, 346.97 2.17 M222.98 144.81 C251.44 112.17, 277.38 81.44, 346.97 2.17 M227.97 145.17 C258.67 109.25, 290.39 71.64, 352.62 1.77 M227.97 145.17 C253.46 115.85, 279.91 85.09, 352.62 1.77 M233.61 144.77 C279.92 90.22, 328.36 36.09, 357.6 2.13 M233.61 144.77 C263.54 109.84, 296.58 72.44, 357.6 2.13 M238.6 145.13 C284.33 94.02, 329.86 38.18, 363.25 1.74 M238.6 145.13 C270.6 110.97, 300.1 77.3, 363.25 1.74 M244.24 144.74 C285.03 98.47, 325.99 49.52, 368.23 2.1 M244.24 144.74 C288.81 94.28, 333.65 43.13, 368.23 2.1 M249.23 145.1 C274.07 113.24, 304.32 83.42, 373.22 2.46 M249.23 145.1 C288.32 102.57, 326.71 58.15, 373.22 2.46 M254.87 144.7 C297.9 94.29, 343.78 43.11, 377.55 3.57 M254.87 144.7 C286.66 106.11, 320.39 66.72, 377.55 3.57 M259.86 145.06 C295.8 103.74, 331.06 61.72, 381.23 5.44 M259.86 145.06 C298.82 101.67, 336.82 56.01, 381.23 5.44 M265.5 144.67 C313.19 90.52, 361.44 33.85, 384.9 7.31 M265.5 144.67 C292.92 114.87, 319.46 83.51, 384.9 7.31 M270.49 145.03 C293.76 118.67, 317.19 90.91, 387.92 9.94 M270.49 145.03 C298.6 112.33, 327.42 78.58, 387.92 9.94 M276.13 144.63 C309.18 107.62, 339.56 70.71, 390.28 13.32 M276.13 144.63 C314.84 98.86, 353.06 54.55, 390.28 13.32 M281.12 144.99 C305.94 116.7, 328.37 88.76, 391.99 17.45 M281.12 144.99 C321.03 97.29, 362.22 51.33, 391.99 17.45 M286.1 145.35 C324.81 103.13, 365.35 57.02, 393.04 22.34 M286.1 145.35 C313.97 112.26, 343.79 77.31, 393.04 22.34 M291.75 144.96 C324.16 107.97, 358.78 71.44, 395.4 25.72 M291.75 144.96 C322.28 110.33, 352.3 74.13, 395.4 25.72 M296.73 145.32 C322.53 113.82, 350.61 86.17, 395.14 32.11 M296.73 145.32 C330.9 104.68, 367.32 64.68, 395.14 32.11 M302.38 144.93 C320.38 122.95, 340.8 101.95, 395.54 37.76 M302.38 144.93 C328.33 112.21, 356.43 82.25, 395.54 37.76 M307.36 145.29 C337.92 110.64, 366.55 76.31, 395.28 44.15 M307.36 145.29 C325.37 123.43, 344.42 100.44, 395.28 44.15 M313.01 144.89 C335.45 120.95, 355.71 93.18, 395.01 50.55 M313.01 144.89 C342.22 110.33, 372.53 76.59, 395.01 50.55 M317.99 145.25 C335.46 125.76, 350.14 107.98, 395.41 56.2 M317.99 145.25 C334.45 126.54, 349.07 108.69, 395.41 56.2 M323.64 144.86 C350.88 112.04, 380.82 81.41, 395.15 62.59 M323.64 144.86 C349.73 114.44, 376.12 84.65, 395.15 62.59 M328.62 145.22 C342.07 126.15, 358.11 110, 395.54 68.24 M328.62 145.22 C352.38 118.21, 373.64 91.69, 395.54 68.24 M334.27 144.82 C356.58 116.53, 380.06 91.2, 395.28 74.63 M334.27 144.82 C352.85 123.85, 373.24 101.61, 395.28 74.63 M339.25 145.18 C350.58 130.51, 366.65 115.79, 395.67 80.28 M339.25 145.18 C360.84 120.94, 381.57 97.17, 395.67 80.28 M344.9 144.79 C359.95 129.93, 374.97 110.51, 395.41 86.68 M344.9 144.79 C359.66 128.95, 373.84 112.99, 395.41 86.68 M349.88 145.15 C365.74 124.35, 384.39 105.92, 395.81 92.32 M349.88 145.15 C365.01 128.41, 378.61 111.44, 395.81 92.32 M355.53 144.75 C366.83 128.33, 381.85 115.58, 395.55 98.72 M355.53 144.75 C366.51 134.09, 376.57 121.06, 395.55 98.72 M360.51 145.11 C371.57 133.46, 379.69 124.34, 395.94 104.36 M360.51 145.11 C371.82 131.23, 384.33 117.41, 395.94 104.36 M364.19 146.98 C372.36 139.23, 375.92 131.75, 395.68 110.76 M364.19 146.98 C375.25 134.47, 383.71 124.14, 395.68 110.76 M370.49 145.83 C377.43 139.68, 383.18 129.21, 396.73 115.65 M370.49 145.83 C377.33 137.24, 382.58 130.01, 396.73 115.65 M377.44 143.93 C380.98 139.22, 385.26 134.38, 391.88 127.33 M377.44 143.93 C381.01 139.93, 385.77 134.2, 391.88 127.33" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C158.41 -0.65, 283.84 1.37, 362 0 M32 0 C99.31 -2.94, 165.01 -1.66, 362 0 M362 0 C382.37 -1.19, 392.06 11.27, 394 32 M362 0 C383.53 0.55, 395.84 12.5, 394 32 M394 32 C396.17 49.29, 394.95 72.1, 394 113 M394 32 C394.74 57.51, 393.79 80.56, 394 113 M394 113 C393.49 135.83, 381.87 143.9, 362 145 M394 113 C391.98 135.04, 383.6 143.19, 362 145 M362 145 C291.31 144.27, 221.15 142.78, 32 145 M362 145 C249.93 143.31, 138.97 142.96, 32 145 M32 145 C8.71 146.69, -1.86 135.95, 0 113 M32 145 C10.21 143.8, 1.65 132.18, 0 113 M0 113 C0.55 94.72, -1.35 75.26, 0 32 M0 113 C-0.88 90.36, -1.5 66.27, 0 32 M0 32 C1.9 12.2, 9.12 -1.48, 32 0 M0 32 C0.22 11.69, 10.1 1.96, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(288.875 73.3828125) rotate(0 140.625 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <empty-blob-digest></text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 0</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> executable: false</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(279.59193843887 271.159696266393) rotate(0 198.99999999999994 130)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C7.06 13.81, 9.24 9.34, 18.1 2.48 M3.66 19.09 C8.44 11.73, 13.76 5.27, 18.1 2.48 M2.75 26.24 C10 16.18, 18.61 6.13, 24.4 1.33 M2.75 26.24 C9.44 19.31, 14.57 12.4, 24.4 1.33 M2.48 32.64 C10.34 21.03, 17.74 12.3, 30.7 0.18 M2.48 32.64 C13.49 21.44, 21.83 9.48, 30.7 0.18 M2.88 38.28 C12.91 25, 26.86 13.76, 34.37 2.05 M2.88 38.28 C12.64 28.15, 21.63 16.63, 34.37 2.05 M2.62 44.68 C14.17 33.27, 22.89 18.23, 39.36 2.41 M2.62 44.68 C12.44 34.48, 21.24 23.55, 39.36 2.41 M2.36 51.07 C18.27 36.19, 31.27 19.8, 45 2.02 M2.36 51.07 C15.34 37.29, 27.12 23.41, 45 2.02 M2.75 56.72 C18.22 37.56, 35.93 17.94, 49.99 2.38 M2.75 56.72 C20.57 34.56, 37.8 14.24, 49.99 2.38 M2.49 63.11 C18.51 41.78, 36.66 23.69, 55.63 1.98 M2.49 63.11 C16.06 46.84, 30.13 32.58, 55.63 1.98 M2.23 69.51 C14.08 54.4, 27.81 42.3, 60.62 2.34 M2.23 69.51 C14.8 54.54, 28.11 38.47, 60.62 2.34 M2.62 75.16 C23.22 53.14, 45.11 28.68, 66.26 1.95 M2.62 75.16 C18.68 55.49, 35.76 36.87, 66.26 1.95 M2.36 81.55 C29.49 51.06, 52.67 20.07, 71.25 2.31 M2.36 81.55 C23.35 59.17, 43.86 35.46, 71.25 2.31 M2.76 87.2 C29.08 59.14, 54.26 30.38, 76.89 1.91 M2.76 87.2 C25.2 61.93, 47.39 35.45, 76.89 1.91 M2.49 93.59 C22.97 73.16, 41.24 50.49, 81.88 2.27 M2.49 93.59 C22.85 70.13, 41.2 48.51, 81.88 2.27 M2.23 99.99 C26.73 70.85, 53 42.34, 87.52 1.88 M2.23 99.99 C26.11 70.65, 52.18 43.37, 87.52 1.88 M2.63 105.64 C29.25 73.43, 57.28 44.11, 92.51 2.24 M2.63 105.64 C22.21 84.02, 40.09 61.9, 92.51 2.24 M2.37 112.03 C24.78 84.92, 46.32 60.61, 98.15 1.85 M2.37 112.03 C40.71 68.73, 77.43 25.35, 98.15 1.85 M2.1 118.43 C32.45 81.76, 64.34 45.79, 103.14 2.21 M2.1 118.43 C26.04 90.13, 50.16 63.23, 103.14 2.21 M2.5 124.07 C34.35 88.85, 66.58 53.31, 108.78 1.81 M2.5 124.07 C35.8 86.3, 67.38 48.42, 108.78 1.81 M2.24 130.47 C44.13 79.64, 85.07 32.68, 113.77 2.17 M2.24 130.47 C28.26 100.65, 54.41 68.41, 113.77 2.17 M2.63 136.11 C39.83 96.08, 73.86 52.5, 119.41 1.78 M2.63 136.11 C27.92 105.97, 55.2 76.2, 119.41 1.78 M2.37 142.51 C38.64 103.62, 72.54 62.27, 124.4 2.14 M2.37 142.51 C38.27 104.7, 71.42 64.68, 124.4 2.14 M2.11 148.91 C31.62 115.14, 60.21 82.46, 130.04 1.74 M2.11 148.91 C30.64 115.22, 60.37 82.49, 130.04 1.74 M2.5 154.55 C45.43 105.72, 87.93 57.79, 135.03 2.1 M2.5 154.55 C39.77 111.59, 78.21 68.92, 135.03 2.1 M2.24 160.95 C40.01 120.72, 75.93 77.72, 140.67 1.71 M2.24 160.95 C44.66 112.93, 87.03 64.56, 140.67 1.71 M1.98 167.35 C39.14 124.53, 74.75 81.57, 145.66 2.07 M1.98 167.35 C47.85 117.91, 91.41 66.21, 145.66 2.07 M2.38 172.99 C58.38 106.95, 115.31 43.43, 150.65 2.43 M2.38 172.99 C53.11 115.34, 104.81 56.32, 150.65 2.43 M2.11 179.39 C64.27 108.42, 124.58 39.52, 156.29 2.03 M2.11 179.39 C42.26 131.15, 83.61 83.43, 156.29 2.03 M2.51 185.03 C62.42 116.85, 122.68 43.77, 161.28 2.39 M2.51 185.03 C56.66 122.43, 112.32 58.31, 161.28 2.39 M2.25 191.43 C51.58 133.03, 102.03 75.72, 166.92 2 M2.25 191.43 C38.3 149.45, 75.45 107.32, 166.92 2 M1.99 197.83 C42.68 152.88, 80.66 108.12, 171.91 2.36 M1.99 197.83 C66.15 123.85, 132.83 47.33, 171.91 2.36 M2.38 203.47 C55.91 143.98, 107.7 83.76, 177.55 1.96 M2.38 203.47 C56.81 142.73, 110.46 81.58, 177.55 1.96 M2.12 209.87 C66.55 137, 131.82 62.14, 182.54 2.32 M2.12 209.87 C50.61 157.03, 98.73 101.44, 182.54 2.32 M1.86 216.27 C63.05 142.96, 126.25 69.69, 188.18 1.93 M1.86 216.27 C65.44 140.95, 131.03 66.87, 188.18 1.93 M2.25 221.91 C59.05 161.57, 111.38 97.22, 193.17 2.29 M2.25 221.91 C52.49 164.23, 101.5 106.36, 193.17 2.29 M1.33 229.06 C64.26 156.57, 124.34 84.79, 198.81 1.9 M1.33 229.06 C60.97 162.1, 120.98 94.62, 198.81 1.9 M1.73 234.71 C54.24 174.7, 106.54 116.97, 203.8 2.26 M1.73 234.71 C76.5 149.79, 152.59 63.83, 203.8 2.26 M2.78 239.59 C49.77 186.82, 94.95 134.94, 209.44 1.86 M2.78 239.59 C83.35 146.44, 163.63 53.44, 209.44 1.86 M4.49 243.73 C48.55 190.47, 93.86 139.53, 214.43 2.22 M4.49 243.73 C63.53 174.61, 122.23 106.2, 214.43 2.22 M6.19 247.86 C77.48 165.73, 149.04 81.52, 220.07 1.83 M6.19 247.86 C85.41 157.55, 164.08 66.86, 220.07 1.83 M8.56 251.24 C90.22 155.68, 175.16 56.51, 225.06 2.19 M8.56 251.24 C89.12 157.29, 172.43 62.13, 225.06 2.19 M10.92 254.62 C69.41 191.54, 124.13 126, 230.7 1.79 M10.92 254.62 C69.41 187.27, 127.03 120.42, 230.7 1.79 M13.94 257.24 C82.45 176.73, 153.61 97.39, 235.69 2.15 M13.94 257.24 C93.45 166.82, 173.91 75.69, 235.69 2.15 M18.27 258.36 C76.98 194.1, 131.53 129.14, 241.33 1.76 M18.27 258.36 C66.67 201.61, 115.46 145.37, 241.33 1.76 M22.6 259.47 C75.95 201, 128.47 140.06, 246.32 2.12 M22.6 259.47 C70.35 207.82, 116.17 155.22, 246.32 2.12 M26.27 261.34 C95.93 180.59, 169.95 95.39, 251.96 1.72 M26.27 261.34 C89.37 190.53, 152.1 118.22, 251.96 1.72 M32.57 260.19 C96.06 189.84, 155.98 120.97, 256.95 2.08 M32.57 260.19 C78.18 205.29, 124.7 151.48, 256.95 2.08 M37.56 260.55 C117 171.22, 192.42 83.99, 262.59 1.69 M37.56 260.55 C121.96 163.6, 206.2 67.19, 262.59 1.69 M43.2 260.16 C105.28 189.09, 169.45 117.92, 267.58 2.05 M43.2 260.16 C123.84 168.01, 203.45 76.43, 267.58 2.05 M48.19 260.52 C117.81 178.99, 186.9 100.6, 272.56 2.41 M48.19 260.52 C125.53 171.84, 202.4 82.45, 272.56 2.41 M53.83 260.12 C138.08 162.28, 222.76 67.81, 278.21 2.01 M53.83 260.12 C128.55 171.85, 204.35 84.41, 278.21 2.01 M58.82 260.48 C127.06 184.42, 193 106.58, 283.19 2.37 M58.82 260.48 C135.91 173.81, 211.1 87.23, 283.19 2.37 M64.46 260.09 C109.71 206.88, 156.75 154.77, 288.84 1.98 M64.46 260.09 C142.84 169.53, 220.7 80.3, 288.84 1.98 M69.45 260.45 C138.09 183.73, 208 105.35, 293.82 2.34 M69.45 260.45 C156.6 162.77, 241.99 64.65, 293.82 2.34 M75.09 260.06 C143.01 182.6, 211.21 103.49, 299.47 1.94 M75.09 260.06 C136 192, 195.87 123.37, 299.47 1.94 M80.08 260.42 C126.17 206.79, 172.95 151.57, 304.45 2.3 M80.08 260.42 C129.25 204.69, 179.83 146.37, 304.45 2.3 M85.72 260.02 C157.76 178.28, 226.22 97.47, 310.1 1.91 M85.72 260.02 C149.05 184.68, 213.67 111.24, 310.1 1.91 M90.71 260.38 C181.48 157.9, 269.25 55.91, 315.08 2.27 M90.71 260.38 C157.59 183.34, 224.55 106.5, 315.08 2.27 M95.7 260.74 C174.5 168.64, 255.03 76.7, 320.73 1.88 M95.7 260.74 C164.52 183.36, 232.23 105.12, 320.73 1.88 M101.34 260.35 C181.6 167.21, 261.15 73.9, 325.71 2.24 M101.34 260.35 C177.77 172.29, 255.39 82.95, 325.71 2.24 M106.33 260.71 C168.93 189.98, 230.06 115.81, 331.36 1.84 M106.33 260.71 C170.53 184.03, 237.17 107.92, 331.36 1.84 M111.97 260.31 C176.35 190.63, 236.85 119.43, 336.34 2.2 M111.97 260.31 C157.57 208.21, 204.38 154.93, 336.34 2.2 M116.96 260.67 C164.3 205.68, 212.63 148.72, 341.99 1.81 M116.96 260.67 C197.94 169.46, 278.58 77.5, 341.99 1.81 M122.6 260.28 C175.27 200.79, 229.84 138.31, 346.97 2.17 M122.6 260.28 C172.5 202.83, 220.76 146.35, 346.97 2.17 M127.59 260.64 C215.15 158.17, 304.42 54.77, 352.62 1.77 M127.59 260.64 C188.96 189.82, 250.22 118.28, 352.62 1.77 M133.23 260.24 C183.32 201.93, 235.94 141.72, 357.6 2.13 M133.23 260.24 C184.28 203.09, 233.15 145.34, 357.6 2.13 M138.22 260.6 C220.44 165.6, 301.95 72.93, 363.25 1.74 M138.22 260.6 C215.16 172.58, 291.69 83.91, 363.25 1.74 M143.86 260.21 C216.13 172.96, 292.05 87.04, 368.23 2.1 M143.86 260.21 C223.54 169.38, 302.36 80.03, 368.23 2.1 M148.85 260.57 C228.87 169.81, 309.22 79.04, 374.53 0.95 M148.85 260.57 C225.85 169.3, 304.04 78.63, 374.53 0.95 M154.49 260.17 C230.89 172.65, 306.45 86.57, 378.21 2.82 M154.49 260.17 C210.94 195.8, 265.6 130.92, 378.21 2.82 M159.48 260.53 C223.94 184.22, 290.75 110.57, 382.54 3.93 M159.48 260.53 C244.05 163.85, 329.27 67.22, 382.54 3.93 M165.12 260.14 C236.95 176.83, 309.56 92.2, 386.87 5.05 M165.12 260.14 C221.23 195.95, 279.16 130.62, 386.87 5.05 M170.11 260.5 C221.21 201.49, 273.24 142.28, 389.23 8.43 M170.11 260.5 C218.37 205.87, 266.44 150.41, 389.23 8.43 M175.75 260.11 C241.58 187.82, 304.45 113.61, 392.25 11.05 M175.75 260.11 C222.69 207.98, 267.39 155.95, 392.25 11.05 M180.74 260.47 C245.33 188.58, 307.54 115.78, 395.27 13.68 M180.74 260.47 C261.6 168.2, 340.34 76.88, 395.27 13.68 M186.38 260.07 C251.57 186.65, 319.28 107.6, 396.32 18.56 M186.38 260.07 C236.65 200.09, 289.45 140.81, 396.32 18.56 M191.37 260.43 C255.8 183.29, 323.42 107.08, 397.37 23.45 M191.37 260.43 C233.86 209.7, 277.09 160.88, 397.37 23.45 M197.01 260.04 C273.9 173.76, 350.14 83.39, 397.77 29.09 M197.01 260.04 C238.49 210.28, 280.91 161.3, 397.77 29.09 M202 260.4 C249.95 204.58, 301.21 145.35, 399.47 33.23 M202 260.4 C246.27 207.11, 292.1 154.9, 399.47 33.23 M207.64 260 C282.04 175.37, 353.03 93.88, 399.21 39.63 M207.64 260 C252.23 206.89, 298.22 154.54, 399.21 39.63 M212.63 260.36 C270.79 190.87, 332.59 125.04, 399.61 45.27 M212.63 260.36 C255.76 212.2, 297.66 164.88, 399.61 45.27 M217.62 260.72 C270.87 200.01, 324.39 137.32, 399.34 51.67 M217.62 260.72 C286.2 182.97, 352.23 105.9, 399.34 51.67 M223.26 260.33 C266.88 209.47, 311.98 157.05, 399.74 57.31 M223.26 260.33 C270.48 203.33, 319.36 147.73, 399.74 57.31 M228.25 260.69 C264.48 217.85, 298.81 176.68, 399.48 63.71 M228.25 260.69 C287.06 192.75, 345.09 124.31, 399.48 63.71 M233.89 260.29 C283.82 202.81, 333.35 147.36, 399.22 70.11 M233.89 260.29 C296.32 189.36, 357.95 117.97, 399.22 70.11 M238.88 260.65 C288.24 201.51, 340.4 144.75, 399.61 75.75 M238.88 260.65 C302.98 187.65, 365.83 115.51, 399.61 75.75 M244.52 260.26 C301.41 192.57, 362.82 123.97, 399.35 82.15 M244.52 260.26 C276.87 221.94, 309.71 183.55, 399.35 82.15 M249.51 260.62 C294.86 210.74, 338.19 159.76, 399.74 87.79 M249.51 260.62 C288.68 213.68, 329.48 168.33, 399.74 87.79 M255.15 260.22 C306.36 203.12, 357.73 144.26, 399.48 94.19 M255.15 260.22 C310.1 197.2, 364.53 133.07, 399.48 94.19 M260.14 260.58 C306.6 208.36, 353.31 154.47, 399.22 100.59 M260.14 260.58 C314.84 198.11, 370.53 134.35, 399.22 100.59 M265.78 260.19 C310.07 209.18, 353.85 155.07, 399.62 106.23 M265.78 260.19 C313.52 203.42, 361.67 148.41, 399.62 106.23 M270.77 260.55 C312.94 210.71, 350.92 165.03, 399.35 112.63 M270.77 260.55 C313.74 212.09, 354.22 165.25, 399.35 112.63 M276.41 260.15 C322.47 205.94, 367.79 155.3, 399.75 118.27 M276.41 260.15 C319.24 210.97, 362.85 162.61, 399.75 118.27 M281.4 260.51 C309.82 228.04, 337.57 195, 399.49 124.67 M281.4 260.51 C315.37 218.59, 351.47 180.05, 399.49 124.67 M287.04 260.12 C329.88 210.1, 376.19 158.03, 399.23 131.06 M287.04 260.12 C314.45 229.24, 339.01 200.91, 399.23 131.06 M292.03 260.48 C332.66 211.87, 372.61 167.01, 399.62 136.71 M292.03 260.48 C317.01 231.17, 342.37 202.7, 399.62 136.71 M297.67 260.09 C320.79 233.82, 342.65 206.24, 399.36 143.11 M297.67 260.09 C337.01 212.55, 377.78 167.34, 399.36 143.11 M302.66 260.45 C335.5 224.45, 365.99 190.46, 399.75 148.75 M302.66 260.45 C341.03 214.95, 379.75 172.24, 399.75 148.75 M308.3 260.05 C331.34 234.81, 350.27 209.28, 399.49 155.15 M308.3 260.05 C337.96 226.28, 369 190.93, 399.49 155.15 M313.29 260.41 C344.16 226.42, 373.26 191.65, 399.23 161.54 M313.29 260.41 C340.87 229.56, 366.67 199.76, 399.23 161.54 M318.93 260.02 C339.08 235.8, 357.34 215.88, 399.63 167.19 M318.93 260.02 C338.18 238.19, 356.44 217.12, 399.63 167.19 M323.92 260.38 C343.38 240.84, 359.55 216.05, 399.36 173.59 M323.92 260.38 C339.58 242.35, 357.05 223.74, 399.36 173.59 M328.9 260.74 C356.6 230.93, 381.38 201.75, 399.76 179.23 M328.9 260.74 C355.35 229.48, 382.07 199.6, 399.76 179.23 M334.55 260.34 C353.55 235.84, 374.53 215.15, 399.5 185.63 M334.55 260.34 C350.96 240.58, 366.19 222.61, 399.5 185.63 M339.53 260.7 C355.36 243.43, 370.27 221.73, 399.24 192.02 M339.53 260.7 C361.66 234.82, 383.39 210.67, 399.24 192.02 M345.18 260.31 C362.54 238.6, 380.33 220.43, 399.63 197.67 M345.18 260.31 C359.44 243.81, 374.08 228.32, 399.63 197.67 M350.16 260.67 C364.5 243.76, 376.71 230.97, 399.37 204.06 M350.16 260.67 C363.93 245.32, 377.91 229.24, 399.37 204.06 M355.81 260.27 C372.91 242.23, 387.92 223.73, 399.76 209.71 M355.81 260.27 C373.85 240.57, 389.06 220.82, 399.76 209.71 M360.79 260.63 C368.09 252.39, 377.96 242.27, 399.5 216.11 M360.79 260.63 C372.53 247.08, 385.83 232.19, 399.5 216.11 M366.44 260.24 C372.92 252.64, 384.19 241.59, 399.24 222.5 M366.44 260.24 C374.31 251.44, 384.03 240.28, 399.24 222.5 M372.74 259.09 C379.45 251.45, 383.63 244.65, 398.98 228.9 M372.74 259.09 C383.24 247.41, 391.87 236.99, 398.98 228.9 M378.38 258.69 C382.28 255.84, 387.86 247.21, 398.06 236.05 M378.38 258.69 C382.66 254.99, 386.79 248.97, 398.06 236.05 M385.99 256.04 C389.48 251.84, 392.18 249.55, 394.52 246.23 M385.99 256.04 C389.44 252.25, 392.29 247.67, 394.52 246.23" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C123.14 1.36, 217.94 1.27, 366 0 M32 0 C131.29 -1.79, 231.25 -1.6, 366 0 M366 0 C387.8 0.82, 396.14 9, 398 32 M366 0 C388.4 -1.28, 396.3 11.05, 398 32 M398 32 C396.68 77, 397.3 120.08, 398 228 M398 32 C397.91 99.78, 396.94 166.68, 398 228 M398 228 C399.84 247.36, 385.48 260.38, 366 260 M398 228 C399.48 250.74, 389.16 259.77, 366 260 M366 260 C278.41 260.83, 191.33 259.2, 32 260 M366 260 C254.85 261.48, 142.72 261.51, 32 260 M32 260 C9.06 261.46, -1.27 247.64, 0 228 M32 260 C8.96 260.25, 0.79 249.13, 0 228 M0 228 C-2.62 187.23, -0.98 150.02, 0 32 M0 228 C-1.12 169.92, -1.74 111.79, 0 32 M0 32 C-0.23 11.83, 11.85 0.53, 32 0 M0 32 C1.74 9.13, 11.06 1.81, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(284.59193843887 276.159696266393) rotate(0 182.81249999999994 124.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: keep</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <directory-with-keep-digest></text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 1</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> executable: false</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="134.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <empty-blob-digest></text><text x="0" y="153.6" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 0</text><text x="0" y="172.79999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> executable: false</text><text x="0" y="192" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks:</text><text x="0" y="211.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: aa</text><text x="0" y="230.39999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> target: /nix/store/somewhereelse</text></g><g stroke-linecap="round" transform="translate(292.58984375 581.578125) rotate(0 192 36.5)"><path d="M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M3.28 12.13 C5.02 8.82, 8.25 4.62, 11.81 2.32 M3.28 12.13 C6.19 8.3, 9.57 5.6, 11.81 2.32 M1.71 20.04 C8.34 16.13, 10.93 7.61, 18.77 0.41 M1.71 20.04 C6.51 15.46, 9.85 9.62, 18.77 0.41 M2.11 25.68 C10.79 16.53, 14.3 8.85, 23.76 0.77 M2.11 25.68 C8.12 18.52, 15.95 11.63, 23.76 0.77 M1.84 32.08 C8.84 22.24, 16.07 17.29, 29.4 0.38 M1.84 32.08 C11.36 20.47, 23.16 9.07, 29.4 0.38 M2.24 37.72 C9.27 29.77, 15.4 22.85, 34.39 0.74 M2.24 37.72 C15.42 23.1, 27.12 8.74, 34.39 0.74 M2.63 43.36 C8.76 33.58, 17 25.79, 40.03 0.35 M2.63 43.36 C13.14 31.06, 23.17 18.26, 40.03 0.35 M2.37 49.76 C16.36 34.07, 29.4 16.32, 45.02 0.71 M2.37 49.76 C17.93 32.43, 33.93 13.74, 45.02 0.71 M2.77 55.41 C19.05 35.42, 38.7 10.68, 50.66 0.31 M2.77 55.41 C19.96 34.91, 39.43 13.51, 50.66 0.31 M1.19 63.31 C17.21 49, 29.42 31.12, 55.65 0.67 M1.19 63.31 C15.9 46.95, 29.88 30.05, 55.65 0.67 M4.21 65.94 C21.41 44.81, 41.02 26.02, 61.29 0.28 M4.21 65.94 C24.28 42.91, 45.96 20.08, 61.29 0.28 M6.57 69.32 C23.78 51.2, 36.27 33.53, 66.28 0.64 M6.57 69.32 C19.35 53.63, 31.93 38.99, 66.28 0.64 M9.59 71.94 C25.17 56.48, 40.49 38.06, 71.92 0.24 M9.59 71.94 C23.84 58.2, 36.53 43.78, 71.92 0.24 M13.92 73.05 C31.98 52.05, 54.85 25.68, 76.91 0.6 M13.92 73.05 C30.95 53.92, 48.62 33.36, 76.91 0.6 M19.57 72.66 C38.35 51.77, 53.43 34.49, 82.55 0.21 M19.57 72.66 C32.02 56.9, 44.97 41.55, 82.55 0.21 M24.55 73.02 C48.11 46.73, 66.96 24.34, 87.54 0.57 M24.55 73.02 C47.62 46.37, 71.59 19.54, 87.54 0.57 M30.2 72.63 C46.25 51.67, 67.22 31.55, 93.18 0.17 M30.2 72.63 C53.38 46.94, 75.41 21.61, 93.18 0.17 M35.18 72.99 C55.38 48.59, 74.75 27.94, 98.17 0.53 M35.18 72.99 C57.1 47.59, 77.7 22.46, 98.17 0.53 M40.83 72.59 C64.45 43.72, 89.12 19.76, 103.81 0.14 M40.83 72.59 C61.26 47.84, 82.21 23.37, 103.81 0.14 M45.81 72.95 C66.24 52.79, 83.08 30.43, 108.8 0.5 M45.81 72.95 C67.8 48.81, 88.58 24.77, 108.8 0.5 M51.46 72.56 C64.09 57.9, 78.25 44.63, 113.78 0.86 M51.46 72.56 C72.83 47.42, 94.74 22.72, 113.78 0.86 M56.44 72.92 C74.65 52.58, 96.04 31.05, 119.43 0.46 M56.44 72.92 C81.73 45.16, 105.23 18.3, 119.43 0.46 M62.09 72.52 C80.07 51.93, 98.45 29.82, 124.41 0.82 M62.09 72.52 C79.04 54.05, 95.96 34.96, 124.41 0.82 M67.07 72.88 C80.12 59.08, 92.94 42.21, 130.06 0.43 M67.07 72.88 C80.66 58.14, 95.45 40.91, 130.06 0.43 M72.06 73.24 C93.79 49.66, 111.38 26.32, 135.04 0.79 M72.06 73.24 C89.58 51.72, 107.82 32.09, 135.04 0.79 M77.7 72.85 C104.72 44.35, 127.17 17, 140.69 0.4 M77.7 72.85 C96.13 51.38, 114.98 29.95, 140.69 0.4 M82.69 73.21 C104.71 47.57, 127.59 22.31, 145.67 0.76 M82.69 73.21 C101.71 51.47, 120.77 29.01, 145.67 0.76 M88.33 72.81 C110.76 47.24, 131.64 20.5, 151.32 0.36 M88.33 72.81 C109 48.14, 130.72 23.09, 151.32 0.36 M93.32 73.17 C111.15 55.01, 126.29 31.78, 156.3 0.72 M93.32 73.17 C110.49 51.39, 129.9 29.91, 156.3 0.72 M98.96 72.78 C118.26 54.28, 133.1 34.79, 161.95 0.33 M98.96 72.78 C111.06 59.03, 124.81 44.07, 161.95 0.33 M103.95 73.14 C115.72 58.92, 129.64 40.85, 166.93 0.69 M103.95 73.14 C126.22 47.2, 149.58 21.56, 166.93 0.69 M109.59 72.74 C123.31 56.85, 140.06 38.01, 172.58 0.29 M109.59 72.74 C124.38 56.56, 137.2 40.37, 172.58 0.29 M114.58 73.1 C138.07 45.66, 163.14 15.75, 177.56 0.65 M114.58 73.1 C131.86 52.94, 147.99 32.77, 177.56 0.65 M120.22 72.71 C133.34 57.46, 150.05 38.7, 183.21 0.26 M120.22 72.71 C135.3 57.12, 148.59 39.51, 183.21 0.26 M125.21 73.07 C147.35 45.64, 169.73 21.54, 188.19 0.62 M125.21 73.07 C146.83 48.56, 167.28 24.07, 188.19 0.62 M130.85 72.68 C149.9 47.27, 173.04 22.78, 193.84 0.22 M130.85 72.68 C153.31 46.45, 175.71 22.68, 193.84 0.22 M135.84 73.04 C157.75 47.97, 181.46 23.22, 198.82 0.58 M135.84 73.04 C157.01 47.37, 178.52 21.52, 198.82 0.58 M141.49 72.64 C163.6 47.54, 184.92 24.28, 203.81 0.94 M141.49 72.64 C157.97 55.11, 171.75 36.25, 203.81 0.94 M146.47 73 C164.74 50.07, 184.75 31.86, 209.45 0.55 M146.47 73 C169.77 45.07, 195.2 17.9, 209.45 0.55 M152.12 72.61 C170.98 50.38, 191.11 25.57, 214.44 0.91 M152.12 72.61 C167.16 54.48, 184.74 36.2, 214.44 0.91 M157.1 72.97 C171.7 55.53, 188.24 37.45, 220.08 0.51 M157.1 72.97 C171.53 57.7, 184.96 42.02, 220.08 0.51 M162.09 73.33 C182.79 52.34, 200.46 29.24, 225.07 0.87 M162.09 73.33 C176.54 57.94, 188.74 43.02, 225.07 0.87 M167.73 72.93 C187.78 52.57, 204.67 31.34, 230.71 0.48 M167.73 72.93 C192.31 45.48, 215.11 18.16, 230.71 0.48 M172.72 73.29 C192.03 53.03, 213.93 26.16, 235.7 0.84 M172.72 73.29 C187.14 54.62, 203.69 37.67, 235.7 0.84 M178.36 72.9 C196.89 49.98, 218.6 27.24, 241.34 0.45 M178.36 72.9 C191.33 57.21, 204.9 42.89, 241.34 0.45 M183.35 73.26 C207.11 47.48, 230.29 17.13, 246.33 0.81 M183.35 73.26 C195.69 56.95, 208.85 41.55, 246.33 0.81 M188.99 72.86 C203.94 55.74, 221.89 34.74, 251.97 0.41 M188.99 72.86 C203.18 55.08, 218.22 38.46, 251.97 0.41 M193.98 73.22 C219.55 44.07, 240.87 19.79, 256.96 0.77 M193.98 73.22 C208.25 55.94, 223.54 39.09, 256.96 0.77 M199.62 72.83 C217.99 48.42, 241.49 28.33, 262.6 0.38 M199.62 72.83 C214.61 56.41, 229.03 40.98, 262.6 0.38 M204.61 73.19 C223.33 53.31, 241.74 30.68, 267.59 0.74 M204.61 73.19 C229.17 46.22, 251.07 19.59, 267.59 0.74 M210.25 72.79 C224.49 54.46, 241.18 34.58, 273.23 0.34 M210.25 72.79 C227.21 52.37, 245.03 32.68, 273.23 0.34 M215.24 73.15 C229.43 57.1, 240.31 42.47, 278.22 0.7 M215.24 73.15 C237.32 48.52, 258.37 22.2, 278.22 0.7 M220.88 72.76 C240.5 49.7, 259.92 29.24, 283.86 0.31 M220.88 72.76 C245.23 46.31, 268.29 19.17, 283.86 0.31 M225.87 73.12 C244.42 48.72, 265.88 27.95, 288.85 0.67 M225.87 73.12 C251.66 44.25, 275.97 16.49, 288.85 0.67 M231.51 72.72 C253.24 45.13, 279.63 17.21, 293.84 1.03 M231.51 72.72 C244.39 57.29, 257.3 41.73, 293.84 1.03 M236.5 73.08 C256.68 52.52, 274.01 31.26, 299.48 0.63 M236.5 73.08 C252.49 52.88, 270.22 34.34, 299.48 0.63 M242.14 72.69 C264.56 48.95, 287.34 23.02, 304.47 0.99 M242.14 72.69 C265.99 45.72, 289.13 17.36, 304.47 0.99 M247.13 73.05 C268.76 49.79, 290 25.19, 310.11 0.6 M247.13 73.05 C271.93 45.31, 297.4 16.35, 310.11 0.6 M252.77 72.66 C272.81 49.72, 292.69 22.65, 315.1 0.96 M252.77 72.66 C274.82 46.14, 296.99 21.06, 315.1 0.96 M257.76 73.02 C279.06 47.65, 296.04 26.13, 320.74 0.56 M257.76 73.02 C279.13 48.78, 298.14 26.64, 320.74 0.56 M262.75 73.38 C286.03 44.74, 309.2 19.59, 325.73 0.92 M262.75 73.38 C284.29 48.03, 306.79 23.96, 325.73 0.92 M268.39 72.98 C284.12 55.13, 298.79 37.14, 331.37 0.53 M268.39 72.98 C286.16 50.17, 306.05 30.27, 331.37 0.53 M273.38 73.34 C296.56 45.22, 323.75 15.16, 336.36 0.89 M273.38 73.34 C289.13 55.59, 302.31 40.33, 336.36 0.89 M279.02 72.95 C303.53 43.87, 326.85 18.13, 342 0.49 M279.02 72.95 C293.37 55.49, 308.15 39.18, 342 0.49 M284.01 73.31 C298.42 57.71, 311.23 40.55, 346.99 0.85 M284.01 73.31 C308.02 43.63, 333.49 16.03, 346.99 0.85 M289.65 72.91 C311.51 49.12, 331.37 27.36, 352.63 0.46 M289.65 72.91 C314.28 43.1, 339.52 15.91, 352.63 0.46 M294.64 73.27 C311.15 55.94, 323.35 38.14, 357.62 0.82 M294.64 73.27 C315.12 50.08, 336.79 25.51, 357.62 0.82 M300.28 72.88 C323.32 47.78, 344.6 22, 363.26 0.43 M300.28 72.88 C320.59 50.02, 339.32 28.35, 363.26 0.43 M305.27 73.24 C321.19 53.97, 335.23 38.9, 368.25 0.79 M305.27 73.24 C320.38 55.99, 334.56 39.62, 368.25 0.79 M310.91 72.84 C326.89 57.23, 339.69 36.32, 372.58 1.9 M310.91 72.84 C323.53 58.18, 338.06 42.96, 372.58 1.9 M315.9 73.2 C339.86 47.6, 361.01 22.6, 376.91 3.02 M315.9 73.2 C338.69 46.17, 361.68 20.58, 376.91 3.02 M321.54 72.81 C338.51 50.61, 357.46 32.27, 379.93 5.64 M321.54 72.81 C336.35 54.99, 349.93 38.93, 379.93 5.64 M326.53 73.17 C341.29 57.14, 355.14 36.64, 382.29 9.02 M326.53 73.17 C347.2 48.97, 367.48 26.48, 382.29 9.02 M332.17 72.77 C348.91 51.78, 366.05 34.35, 384.66 12.4 M332.17 72.77 C345.89 56.87, 360.02 41.97, 384.66 12.4 M337.16 73.13 C350.96 56.83, 362.64 44.64, 384.39 18.8 M337.16 73.13 C350.38 58.41, 363.8 42.97, 384.39 18.8 M342.8 72.74 C358.9 55.85, 372.92 38.47, 384.13 25.19 M342.8 72.74 C359.81 54.2, 374.02 35.6, 384.13 25.19 M347.79 73.1 C354.66 65.33, 364.11 55.68, 384.53 30.84 M347.79 73.1 C358.88 60.26, 371.55 46.08, 384.53 30.84 M352.77 73.46 C358.94 66.21, 369.89 55.52, 384.27 37.23 M352.77 73.46 C360.31 65.04, 369.69 54.26, 384.27 37.23 M358.42 73.07 C364.98 65.59, 369.02 58.96, 384 43.63 M358.42 73.07 C368.68 61.66, 377.08 51.51, 384 43.63 M363.4 73.43 C367.59 70.26, 373.45 61.3, 384.4 49.27 M363.4 73.43 C367.97 69.41, 372.38 63.07, 384.4 49.27 M367.74 74.54 C374.28 66.78, 379.58 61.94, 384.14 55.67 M367.74 74.54 C374.31 67.19, 380 58.66, 384.14 55.67 M374.69 72.64 C376.19 69.99, 382.01 66.54, 385.19 60.56 M374.69 72.64 C377.37 68.95, 380.91 65.58, 385.19 60.56" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M18.25 0 M18.25 0 C129.54 2.72, 242.28 1.14, 365.75 0 M18.25 0 C127.66 -1.41, 238.58 -1.21, 365.75 0 M365.75 0 C378.27 0.9, 382.95 5.36, 384 18.25 M365.75 0 C380.07 0.47, 383.93 4.97, 384 18.25 M384 18.25 C384.02 33.84, 384.87 48.45, 384 54.75 M384 18.25 C384.01 28.7, 384.22 39.53, 384 54.75 M384 54.75 C382.32 67.61, 378.7 73.98, 365.75 73 M384 54.75 C385.93 68.19, 378.42 74.36, 365.75 73 M365.75 73 C288.24 70.59, 213.1 72.01, 18.25 73 M365.75 73 C265.08 74.84, 163.23 75.38, 18.25 73 M18.25 73 C7.8 72.9, 1.58 68.54, 0 54.75 M18.25 73 C7.06 72.31, -0.46 65.42, 0 54.75 M0 54.75 C0.31 45.73, -1.55 34.03, 0 18.25 M0 54.75 C0.72 40.09, -0.05 27.49, 0 18.25 M0 18.25 C1.55 7.28, 6.06 -1.74, 18.25 0 M0 18.25 C1.91 4.6, 5.29 1.6, 18.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(297.58984375 586.578125) rotate(0 70.3125 28.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(297.01171875 700.19140625) rotate(0 192 63)"><path d="M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M1.14 22.19 C5.68 16.12, 10.34 11.35, 18.19 2.57 M1.14 22.19 C5.63 15.12, 11.63 10.54, 18.19 2.57 M2.84 26.32 C9.4 19.34, 14.38 14.47, 24.49 1.42 M2.84 26.32 C7.02 21.88, 12.43 16.24, 24.49 1.42 M3.24 31.97 C12.37 22.32, 23.31 9.97, 30.14 1.02 M3.24 31.97 C8.75 26.69, 14.07 19.43, 30.14 1.02 M2.98 38.36 C11.61 29.09, 22.34 16.73, 34.47 2.14 M2.98 38.36 C14.61 26.7, 24.62 13.57, 34.47 2.14 M2.72 44.76 C8.44 35.48, 20.15 28.58, 39.46 2.5 M2.72 44.76 C9.81 35.38, 16.68 27.81, 39.46 2.5 M3.11 50.4 C17.54 33.89, 29.94 20.31, 45.1 2.1 M3.11 50.4 C20.74 31.63, 36.06 13.37, 45.1 2.1 M2.85 56.8 C18.58 35.97, 39.38 14.61, 50.09 2.46 M2.85 56.8 C15.02 42.55, 25.62 29.03, 50.09 2.46 M3.24 62.45 C13.28 49.17, 26.13 38.7, 55.73 2.07 M3.24 62.45 C14.41 50.21, 23.72 38.66, 55.73 2.07 M2.98 68.84 C13.36 56.63, 24.49 40.18, 60.72 2.43 M2.98 68.84 C25.38 43.36, 46.78 18.82, 60.72 2.43 M2.72 75.24 C27.68 45.78, 49.87 21.46, 66.36 2.03 M2.72 75.24 C27.36 48.58, 50.99 21.41, 66.36 2.03 M3.12 80.88 C26.98 53.98, 51.79 26.74, 71.35 2.39 M3.12 80.88 C24.82 57.11, 46.21 30.7, 71.35 2.39 M2.85 87.28 C25.83 58.82, 49.6 32.81, 76.33 2.75 M2.85 87.28 C18.86 69.84, 32.85 53.27, 76.33 2.75 M3.25 92.92 C31.02 64.11, 55.21 32.05, 81.98 2.36 M3.25 92.92 C29.09 64.51, 54.01 35.08, 81.98 2.36 M0.36 102.34 C18.06 82.88, 34.35 63.19, 86.96 2.72 M0.36 102.34 C22.5 76.81, 45.99 50.02, 86.96 2.72 M2.07 106.47 C35.94 64.04, 71.16 24.59, 92.61 2.33 M2.07 106.47 C29.3 74.98, 57.21 42, 92.61 2.33 M3.78 110.61 C32.64 72.16, 65.08 38.52, 97.59 2.69 M3.78 110.61 C27.71 83.59, 50.59 56.73, 97.59 2.69 M5.48 114.74 C31.81 84.38, 61.24 51.3, 103.24 2.29 M5.48 114.74 C38.37 76.78, 69.65 40.61, 103.24 2.29 M7.85 118.12 C37.24 84.78, 67.86 51.75, 108.22 2.65 M7.85 118.12 C40.51 79.72, 74.23 40.65, 108.22 2.65 M10.86 120.75 C33.15 93.28, 59.27 65.67, 113.87 2.26 M10.86 120.75 C41.44 83.49, 74.67 47.06, 113.87 2.26 M14.54 122.62 C54.9 77.33, 93.45 31.72, 118.85 2.62 M14.54 122.62 C36.96 96.13, 58.59 69.9, 118.85 2.62 M18.21 124.48 C60.03 78.24, 98.36 27.73, 124.5 2.22 M18.21 124.48 C47.99 89.82, 78.58 52.73, 124.5 2.22 M23.2 124.84 C60.93 81.34, 96.9 37.41, 129.48 2.58 M23.2 124.84 C51.15 90.76, 80.7 57.55, 129.48 2.58 M27.53 125.96 C67.64 79.48, 108.95 32.16, 135.13 2.19 M27.53 125.96 C50.35 100, 70.45 76.16, 135.13 2.19 M32.52 126.32 C67.96 82.8, 105.29 42.95, 140.11 2.55 M32.52 126.32 C55.09 102.84, 75.65 76.61, 140.11 2.55 M37.51 126.68 C76.58 83.49, 111.39 39.46, 145.76 2.15 M37.51 126.68 C68.68 90.35, 99.78 54.4, 145.76 2.15 M43.15 126.29 C79.05 86.81, 113.32 46.01, 150.74 2.51 M43.15 126.29 C83.16 78.74, 122.47 32.63, 150.74 2.51 M48.14 126.65 C84.95 86.46, 121.01 43.96, 156.39 2.12 M48.14 126.65 C77.47 92.07, 106.15 57.86, 156.39 2.12 M53.12 127.01 C94.06 83.46, 132.13 35.83, 161.37 2.48 M53.12 127.01 C83.35 90.25, 115.88 54.9, 161.37 2.48 M58.77 126.61 C88.9 91.94, 120.62 55.5, 167.02 2.08 M58.77 126.61 C80.71 101.71, 103.08 74.94, 167.02 2.08 M63.75 126.97 C94.83 94.26, 121.35 59.65, 172 2.44 M63.75 126.97 C99.19 87.16, 134.08 46.33, 172 2.44 M69.4 126.58 C96.06 98.29, 123.08 66.58, 177.65 2.05 M69.4 126.58 C94.05 98.86, 118.43 72.82, 177.65 2.05 M74.38 126.94 C110.22 87.09, 146.32 44.12, 182.63 2.41 M74.38 126.94 C112.7 80.36, 152.52 34.24, 182.63 2.41 M80.03 126.54 C112.63 85.84, 146.64 47.21, 188.28 2.01 M80.03 126.54 C118.52 83.47, 154.62 39.47, 188.28 2.01 M85.01 126.9 C123.66 84.49, 160.64 41.03, 193.26 2.37 M85.01 126.9 C107.24 101.3, 128.84 75.24, 193.26 2.37 M90.66 126.51 C128.32 79.98, 168.35 34.11, 198.25 2.73 M90.66 126.51 C125.23 87.51, 159.47 48.47, 198.25 2.73 M95.64 126.87 C136.32 80.89, 176.13 32.73, 203.89 2.34 M95.64 126.87 C130.67 85.7, 167.03 45.03, 203.89 2.34 M101.29 126.47 C138.72 85.68, 174.7 42.24, 208.88 2.7 M101.29 126.47 C141.27 81.26, 181.48 32.8, 208.88 2.7 M106.27 126.83 C133.13 96.99, 159.87 69.21, 214.52 2.31 M106.27 126.83 C139.22 87.92, 173.99 50.04, 214.52 2.31 M111.92 126.44 C139.17 92.95, 170.03 64.03, 219.51 2.67 M111.92 126.44 C140.35 94.34, 169.24 63.52, 219.51 2.67 M116.9 126.8 C139.41 102.01, 161.49 74.65, 225.15 2.27 M116.9 126.8 C143.96 97.47, 169.6 65.58, 225.15 2.27 M122.55 126.4 C151.81 92.7, 184.46 60.16, 230.14 2.63 M122.55 126.4 C144.55 101.98, 167.71 77.15, 230.14 2.63 M127.53 126.76 C155.24 93.9, 184.87 59.39, 235.78 2.24 M127.53 126.76 C162.55 85.69, 197.14 44.32, 235.78 2.24 M133.18 126.37 C156.24 97.59, 181.25 74.22, 240.77 2.6 M133.18 126.37 C169.91 83.87, 205.04 43.06, 240.77 2.6 M138.16 126.73 C171.62 83.67, 210.01 43.26, 246.41 2.2 M138.16 126.73 C162.75 98.55, 185.88 71.86, 246.41 2.2 M143.81 126.33 C167.53 96.38, 195.44 68.77, 251.4 2.56 M143.81 126.33 C175.38 87.65, 208.52 52.45, 251.4 2.56 M148.79 126.69 C186.15 81.29, 226.32 36.57, 257.04 2.17 M148.79 126.69 C185.38 86.13, 221.92 44.72, 257.04 2.17 M154.44 126.3 C190.78 83.15, 229.34 36.03, 262.03 2.53 M154.44 126.3 C178.51 97.01, 203.83 70.69, 262.03 2.53 M159.42 126.66 C189.83 93.77, 217.8 63.16, 267.67 2.13 M159.42 126.66 C196.22 84.11, 234.53 42.17, 267.67 2.13 M165.07 126.27 C199.58 88.17, 233.46 45.58, 272.66 2.49 M165.07 126.27 C204.81 83.18, 243.36 38.31, 272.66 2.49 M170.05 126.63 C191.4 102.17, 214.65 75.55, 278.3 2.1 M170.05 126.63 C194.79 98.87, 219.21 68.85, 278.3 2.1 M175.04 126.99 C218.62 76.89, 259.45 31.99, 283.29 2.46 M175.04 126.99 C200.78 100.7, 225.33 71.6, 283.29 2.46 M180.68 126.59 C214.02 88.6, 246.67 51.74, 288.93 2.06 M180.68 126.59 C212.29 91.19, 241.62 56.43, 288.93 2.06 M185.67 126.95 C215.71 95.07, 243.49 60.99, 293.92 2.42 M185.67 126.95 C216.01 94.84, 243.45 62.69, 293.92 2.42 M191.31 126.56 C229.63 85.76, 263.97 41.71, 299.56 2.03 M191.31 126.56 C222.59 88.19, 255.96 51.19, 299.56 2.03 M196.3 126.92 C217.19 100.03, 243 74.87, 304.55 2.39 M196.3 126.92 C218.88 99.48, 243.28 72.58, 304.55 2.39 M201.94 126.52 C245.64 78.21, 284.33 30.24, 309.54 2.75 M201.94 126.52 C240.53 84.15, 277.64 41.48, 309.54 2.75 M206.93 126.88 C248.75 80.42, 287.71 35.97, 315.18 2.36 M206.93 126.88 C240.59 87.82, 273.05 49.59, 315.18 2.36 M212.57 126.49 C242.06 93.98, 270.55 61.9, 320.17 2.72 M212.57 126.49 C238.53 99.42, 261.94 72.17, 320.17 2.72 M217.56 126.85 C255.21 80.1, 296.4 36.1, 325.81 2.32 M217.56 126.85 C248.46 91.27, 280.97 56.5, 325.81 2.32 M223.2 126.45 C245.32 96.89, 272.92 68.02, 330.8 2.68 M223.2 126.45 C246.93 99.22, 269.26 73.84, 330.8 2.68 M228.19 126.81 C266.27 81.56, 302.8 39.44, 336.44 2.29 M228.19 126.81 C268.68 80.87, 310.31 31.49, 336.44 2.29 M233.83 126.42 C261.99 92.9, 293.86 58.89, 341.43 2.65 M233.83 126.42 C265.72 90.09, 297.62 53.24, 341.43 2.65 M238.82 126.78 C276.56 81.01, 314.76 40.31, 347.07 2.25 M238.82 126.78 C275.07 83.85, 309.78 43.16, 347.07 2.25 M244.47 126.38 C281.08 86.31, 311.83 47.08, 353.37 1.1 M244.47 126.38 C284.42 79.98, 324.2 34.41, 353.37 1.1 M249.45 126.74 C283.83 89.61, 316.66 48.25, 358.36 1.46 M249.45 126.74 C288.06 81.49, 329.09 35.62, 358.36 1.46 M255.1 126.35 C286.95 88.5, 321.78 49.74, 362.69 2.58 M255.1 126.35 C278.54 101.51, 299.57 76.6, 362.69 2.58 M260.08 126.71 C289.9 92.16, 318.76 59.09, 367.68 2.94 M260.08 126.71 C286.6 97.68, 309.24 69.16, 367.68 2.94 M265.73 126.32 C304.67 81.34, 343.89 36.13, 371.35 4.81 M265.73 126.32 C296.05 89.95, 328.32 54.46, 371.35 4.81 M270.71 126.68 C312.11 79.85, 350.1 38.08, 375.03 6.68 M270.71 126.68 C308.29 85.27, 344.32 43.1, 375.03 6.68 M276.36 126.28 C302.37 95.05, 332.55 61.6, 377.39 10.06 M276.36 126.28 C300.17 99.46, 322.39 72.19, 377.39 10.06 M281.34 126.64 C304.76 96.82, 332.3 67.75, 380.41 12.68 M281.34 126.64 C307.49 95.18, 332.91 65.5, 380.41 12.68 M286.33 127 C314.42 93.01, 345.57 57.56, 381.46 17.57 M286.33 127 C313.51 94.85, 341.77 62.68, 381.46 17.57 M291.97 126.61 C313.48 100.45, 339.17 75.97, 383.16 21.7 M291.97 126.61 C311.58 103.09, 333.11 79.56, 383.16 21.7 M296.96 126.97 C320.59 100.34, 342.62 72.36, 384.87 25.84 M296.96 126.97 C321.66 99.71, 344.27 72.56, 384.87 25.84 M302.6 126.57 C334.11 86.35, 368.78 51.97, 385.27 31.48 M302.6 126.57 C330.75 94.29, 357.85 62.13, 385.27 31.48 M307.59 126.93 C326.81 105.47, 343.03 83.78, 385 37.88 M307.59 126.93 C327.54 105.51, 344.6 84.21, 385 37.88 M313.23 126.54 C332.05 106.16, 349.05 86.64, 385.4 43.52 M313.23 126.54 C332.89 103.96, 352.85 83.32, 385.4 43.52 M318.22 126.9 C331.98 110.58, 346.57 93.14, 385.14 49.92 M318.22 126.9 C331.03 111.1, 344.19 94.95, 385.14 49.92 M323.86 126.5 C335.72 108.89, 350.56 93.22, 385.53 55.56 M323.86 126.5 C345.11 101.47, 364.04 79.57, 385.53 55.56 M328.85 126.86 C346.1 108.02, 361.21 90.1, 385.27 61.96 M328.85 126.86 C342.2 112.06, 355.94 96.74, 385.27 61.96 M334.49 126.47 C350.84 109.97, 364.71 94.6, 385.67 67.6 M334.49 126.47 C353.49 104.81, 372.17 82.14, 385.67 67.6 M339.48 126.83 C348.96 114.71, 363.17 101.01, 385.4 74 M339.48 126.83 C354.75 110.09, 368.15 93.09, 385.4 74 M345.12 126.43 C354.84 117.22, 365.07 103.77, 385.8 79.64 M345.12 126.43 C358.46 110.23, 371.31 95.91, 385.8 79.64 M350.11 126.79 C364.3 111.08, 375.22 97.18, 385.54 86.04 M350.11 126.79 C363.14 112.28, 374.94 98.31, 385.54 86.04 M354.44 127.91 C362.81 117.31, 370.87 110.18, 385.28 92.44 M354.44 127.91 C362.9 118.85, 369.83 111.91, 385.28 92.44 M360.74 126.76 C367.63 119.85, 374.61 113.88, 386.98 96.57 M360.74 126.76 C369 116.72, 377.31 108.85, 386.98 96.57 M367.7 124.86 C371.04 118.25, 376.15 112.61, 382.13 108.25 M367.7 124.86 C371.11 120.48, 374.22 117.39, 382.13 108.25" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M31.5 0 M31.5 0 C144.73 2.16, 257.89 1.55, 352.5 0 M31.5 0 C123.21 -0.03, 215.81 -0.04, 352.5 0 M352.5 0 C373.48 -1.54, 384.54 11.54, 384 31.5 M352.5 0 C371.52 1.89, 382.06 11.18, 384 31.5 M384 31.5 C384.2 57.65, 382.83 81.45, 384 94.5 M384 31.5 C384.94 50.55, 383.94 67.7, 384 94.5 M384 94.5 C385.07 116.86, 374.35 127.03, 352.5 126 M384 94.5 C385.17 117.04, 373.57 125.11, 352.5 126 M352.5 126 C247.11 128.47, 140.42 127.09, 31.5 126 M352.5 126 C271.97 126.76, 193.29 127.03, 31.5 126 M31.5 126 C11.34 124.76, -1.26 114.07, 0 94.5 M31.5 126 C9.77 126.57, -1.64 113.31, 0 94.5 M0 94.5 C0.36 74.76, 1.16 53.5, 0 31.5 M0 94.5 C-0.19 81.51, -0.09 68.38, 0 31.5 M0 31.5 C-0.91 8.69, 10.9 0.79, 31.5 0 M0 31.5 C2.16 12.64, 11.39 -0.47, 31.5 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(302.01171875 705.19140625) rotate(0 145.3125 57.60000000000002)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: a</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> digest: <directory-a-digest></text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> size: 0</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g transform="translate(303.75390625 45.62890625) rotate(0 89.0625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_WITH_KEEP</text></g><g transform="translate(308.25390625 240.4765625) rotate(0 98.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_COMPLICATED</text></g><g transform="translate(307.28125 554.6148437500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_A</text></g><g transform="translate(310.6875 674.4585937500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_B</text></g><g transform="translate(18.95703125 42.53671874999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_A</text></g><g transform="translate(22.55078125 130.24374999999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_B</text></g><g transform="translate(13.5546875 210.27109374999998) rotate(0 46.875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_EMPTY</text></g><g stroke-linecap="round"><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-0.52 1.13 C-60.08 19.9, -297.46 94.83, -357.2 113.61 M1.4 0.68 C-58.25 18.98, -298.08 93.09, -357.74 111.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-331.96 94.73 C-340.78 97.36, -347.89 102.93, -357.37 112.7 M-333.95 93.43 C-339.75 97.47, -345.42 102.99, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-325.89 114.33 C-336.76 110.86, -345.74 110.35, -357.37 112.7 M-327.89 113.04 C-335.08 112.25, -342.26 112.89, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M-0.42 1.04 C43.76 -38.95, 220.7 -199.19, 264.79 -239.05 M1.55 0.54 C45.63 -39.42, 220.36 -198.5, 263.94 -238.07" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M251.72 -211.19 C251.81 -219.53, 257.18 -225.36, 265.07 -236.65 M250.73 -212.1 C253.06 -218.02, 257.3 -223.31, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M237.91 -226.37 C241.88 -230.52, 251.02 -232.22, 265.07 -236.65 M236.92 -227.29 C242.47 -229.5, 250.03 -231.14, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M0.21 0.21 C-60.17 -24.98, -301.37 -126.03, -361.5 -151.72 M-1.14 -0.73 C-61.8 -26.18, -302.73 -127.84, -362.74 -153.25" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-332.43 -152.64 C-341.83 -151.66, -356.48 -151.02, -362.88 -151.77 M-332.23 -151.12 C-344.25 -151.99, -356 -152.45, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-340.42 -133.74 C-346.81 -139.68, -358.54 -145.95, -362.88 -151.77 M-340.22 -132.22 C-349.22 -140.26, -357.91 -147.96, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M-0.84 0.01 C13.92 -16, 74.3 -80.87, 89.45 -97.18 M0.92 -1.04 C15.95 -16.73, 76.9 -79.62, 91.58 -95.58" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M79.65 -69.91 C84.59 -79.16, 87.99 -87.88, 90.6 -97.23 M78.91 -67.92 C83.62 -76.08, 87.25 -85.83, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M64.75 -84.03 C75.18 -87.85, 84.2 -91.25, 90.6 -97.23 M64.01 -82.04 C73.25 -85.89, 81.47 -91.29, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask></svg> \ No newline at end of file |