Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ build
.cache/
compile_commands.json
photon_sysroot*
local_sysroot_*
sysroot*/
build-pi/*
cmake_build/*
.gradle/*
*.jar
.m2-arm64/
46 changes: 33 additions & 13 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,24 @@ find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED COMPONENTS OpenGL EGL)
pkg_check_modules(LIBDRM REQUIRED libdrm)
pkg_check_modules(LIBCAMERA REQUIRED libcamera)

# Some downstream builds (e.g. OV9782 on Raspberry Pi images) may require newer
# libcamera packages, but we keep the build compatible with older libcamera-dev
# by default to avoid breaking existing environments.
option(REQUIRE_LIBCAMERA_0_6 "Require libcamera >= 0.6 (enforced via pkg-config)" OFF)
set(LIBCAMERA_MIN_VERSION "0.6" CACHE STRING "Minimum libcamera version when REQUIRE_LIBCAMERA_0_6 is ON")
if (REQUIRE_LIBCAMERA_0_6)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for 2027+, we should force everyone to use a new libcamera?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the over arching goals are, so I did not want to force 0.6 if there was other issues with other platforms. I would recommend it as 0.6+ is stable now to my knowledge

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully this fixes (unofficial) Debian/RPiOS Trixie support again. I don't have the time to check this today, but maybe tomorrow.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we currently install libcamera 0.5.2 -> https://github.com/PhotonVision/photon-image-modifier/actions/runs/22130709321/job/64030036864?pr=126#step:5:1477

I think @Gold856 was partially through compiling libcamera from source as part of this driver build, which would release us from our dependence on random rpi apt repos.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RPiOS Trixie image has libcamera 0.6 preinstalled. While libcamera 0.5 is available, sufficiently removing 0.6 to install 0.5 is nontrivial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I got libcamera 0.6 building completely independently. That's what I want to switch to for 2027. Still trying to work out how to ship the IPA files and stuff.

pkg_check_modules(LIBCAMERA REQUIRED "libcamera>=${LIBCAMERA_MIN_VERSION}")
else ()
pkg_check_modules(LIBCAMERA REQUIRED libcamera)
if (DEFINED LIBCAMERA_VERSION AND LIBCAMERA_VERSION VERSION_LESS "0.6")
message(
WARNING
"Found libcamera ${LIBCAMERA_VERSION}. Some downstream targets/workflows may require libcamera >= 0.6. "
"To enforce a minimum, configure with -DREQUIRE_LIBCAMERA_0_6=ON (and optionally -DLIBCAMERA_MIN_VERSION=0.6)."
)
endif ()
endif ()
pkg_check_modules(LIBGBM REQUIRED gbm)

set(OPENCV_YEAR "frc2025")
Expand Down Expand Up @@ -90,15 +107,18 @@ target_link_libraries(
)
target_compile_options(photonlibcamera PRIVATE -Wall -Wextra -Wpedantic -Werror)

add_executable(libcamera_meme main.cpp)
target_include_directories(
libcamera_meme
PUBLIC
${OPENGL_INCLUDE_DIRS}
${LIBDRM_INCLUDE_DIRS}
${LIBCAMERA_INCLUDE_DIRS}
${LIBGBM_INCLUDE_DIRS}
${JNI_INCLUDE_DIRS}
)
target_include_directories(libcamera_meme SYSTEM PUBLIC ${OPENCV_INCLUDE_PATH})
target_link_libraries(libcamera_meme photonlibcamera)
option(BUILD_CAMERA_MEME "Build the libcamera_meme test binary" ON)
if (BUILD_CAMERA_MEME)
add_executable(libcamera_meme main.cpp)
target_include_directories(
libcamera_meme
PUBLIC
${OPENGL_INCLUDE_DIRS}
${LIBDRM_INCLUDE_DIRS}
${LIBCAMERA_INCLUDE_DIRS}
${LIBGBM_INCLUDE_DIRS}
${JNI_INCLUDE_DIRS}
)
target_include_directories(libcamera_meme SYSTEM PUBLIC ${OPENCV_INCLUDE_PATH})
target_link_libraries(libcamera_meme photonlibcamera)
endif ()
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,36 @@ We use CMake for our builds. The build should output both a shared library, `lib
```
sudo apt-get update
sudo apt-get install -y default-jdk libopencv-dev libegl1-mesa-dev libcamera-dev cmake build-essential libdrm-dev libgbm-dev openjdk-17-jdk

git clone https://github.com/PhotonVision/photon-libcamera-gl-driver.git
```

Note: Some downstream targets/workflows (including OV9782 on Raspberry Pi images) may
require libcamera >= 0.6. By default, this project will build against whatever
`libcamera-dev` you have installed; to enforce >= 0.6 at configure time, pass:

```
cmake -B cmake_build -S . -DREQUIRE_LIBCAMERA_0_6=ON
```

Build with the following cmake commands:

```
cd photon-libcamera-gl-driver
mkdir cmake_build
cmake -B cmake_build -S .
cmake --build cmake_build
./gradlew build publishtomavenlocal
./gradlew build publishToMavenLocal
```

## Arm64 build with sysroot (Docker)

If you are building on an x86_64 host and need an arm64 JNI that links against
libcamera (often >= 0.6) from a target rootfs, run:

```
SYSROOT_DIR=/path/to/target/rootfs \
MAVEN_LOCAL_REPO=/path/to/m2 \
tools/build_arm64_jni.sh
```

## Running eglinfo
Expand Down
4 changes: 3 additions & 1 deletion include/camera_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#pragma once

#include <string>
#include <string_view>
#include <vector>

enum CameraModel {
Expand All @@ -28,7 +29,8 @@ enum CameraModel {
IMX477, // Picam HQ
OV9281,
OV7251,
OV9782,
Unknown
};
CameraModel stringToModel(const std::string &model);
CameraModel stringToModel(std::string_view model);
bool isGrayScale(CameraModel model);
19 changes: 14 additions & 5 deletions src/camera_grabber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,20 @@ void CameraGrabber::setControls(libcamera::Request *request) {
using namespace libcamera;

auto &controls_ = request->controls();
if (m_model != OV9281) {
controls_.set(controls::AwbEnable, false); // AWB disabled
const auto &control_info = m_camera->controls();
const bool has_awb =
control_info.find(&controls::AwbEnable) != control_info.end();
const bool is_mono_sensor =
(m_model == OV7251) || (m_model == OV9281 && !has_awb);

if (has_awb) {
controls_.set(controls::AwbEnable, !is_mono_sensor);
}
controls_.set(controls::AnalogueGain,
m_settings.analogGain); // Analog gain, min 1 max big number?

if (m_model != OV9281) {
if (!is_mono_sensor &&
control_info.find(&controls::ColourGains) != control_info.end()) {
controls_.set(controls::ColourGains,
libcamera::Span<const float, 2>{
{m_settings.awbRedGain,
Expand All @@ -164,7 +171,8 @@ void CameraGrabber::setControls(libcamera::Request *request) {
controls_.set(controls::Contrast,
m_settings.contrast); // Nominal 1

if (m_model != OV9281) {
if (!is_mono_sensor &&
control_info.find(&controls::Saturation) != control_info.end()) {
controls_.set(controls::Saturation,
m_settings.saturation); // Nominal 1, 0 would be greyscale
}
Expand Down Expand Up @@ -197,7 +205,8 @@ void CameraGrabber::setControls(libcamera::Request *request) {

controls_.set(controls::ExposureValue, 0);

if (m_model != OV7251 && m_model != OV9281) {
if (!is_mono_sensor &&
control_info.find(&controls::Sharpness) != control_info.end()) {
controls_.set(controls::Sharpness, 1);
}
}
Expand Down
22 changes: 12 additions & 10 deletions src/camera_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,26 @@
#include <algorithm>
#include <cstring>
#include <string>
#include <string_view>

static const CameraModel grayScaleCameras[] = {OV9281};
static const CameraModel grayScaleCameras[] = {OV9281, OV7251};

CameraModel stringToModel(const std::string &model) {
const char *famname = model.c_str();
if (!strcmp(famname, "ov5647"))
CameraModel stringToModel(std::string_view model) {
if (model == "ov5647")
return OV5647;
else if (!strcmp(famname, "imx219"))
else if (model == "imx219")
return IMX219;
else if (!strcmp(famname, "imx708"))
else if (model == "imx708")
return IMX708;
else if (!strcmp(famname, "imx477"))
else if (model == "imx477")
return IMX477;
else if (!strcmp(famname, "ov9281"))
else if (model == "ov9281")
return OV9281;
else if (!strcmp(famname, "ov7251"))
else if (model == "ov9782")
return OV9782;
else if (model == "ov7251")
return OV7251;
else if (!strcmp(famname, "Disconnected"))
else if (model == "Disconnected")
return Disconnected;
else
return Unknown;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/photonvision/raspi/LibCameraJNI.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum SensorModel {
IMX477, // Picam HQ
OV9281,
OV7251,
OV9782,
Unknown;

public String getFriendlyName() {
Expand All @@ -44,6 +45,8 @@ public String getFriendlyName() {
return "OV9281";
case OV7251:
return "OV7251";
case OV9782:
return "OV9782";
case Unknown:
default:
return "Unknown Camera";
Expand Down
134 changes: 134 additions & 0 deletions tools/build_arm64_jni.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/bin/bash
Copy link

@mcm001 mcm001 Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remind me why we should upstream this script, rather than the existing cross build setup? (Which granted only really works in CI or a real Pi)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes local dev much quicker and possible without access to direct hardware. I can swap it out with a full docker container so other environments can build easier or if its not wanted can be removed all together.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think that the current cross-compiling story kinda sucks/isn't there, so I do see value in having a blessed way to build outside of the pipeline. I'll think a bit harder on it but that broadly makes sense to me

set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

sysroot_dir="${SYSROOT_DIR:-}"
if [ -z "${sysroot_dir}" ]; then
echo "SYSROOT_DIR is required (path to the target rootfs or sysroot)." 1>&2
exit 1
fi
sysroot_dir="$(realpath "${sysroot_dir}")"

docker_image="${DOCKER_IMAGE:-debian:bookworm}"
docker_platform="${DOCKER_PLATFORM:-linux/arm64}"
maven_local_repo="${MAVEN_LOCAL_REPO:-${repo_root}/.m2-arm64}"
cmake_extra_args="${CMAKE_EXTRA_ARGS:-}"
gradle_extra_args="${GRADLE_EXTRA_ARGS:-}"
clean_build="${CLEAN_BUILD:-0}"
sysroot_mode="${SYSROOT_MODE:-libcamera}"
use_docker="${USE_DOCKER:-1}"

if [ "${clean_build}" = "1" ]; then
rm -rf "${repo_root}/cmake_build" || true
fi

mkdir -p "${maven_local_repo}"

if [ "${use_docker}" != "1" ]; then
export DEBIAN_FRONTEND=noninteractive
export PKG_CONFIG_SYSROOT_DIR="${sysroot_dir}"
export PKG_CONFIG_PATH="${sysroot_dir}/usr/lib/aarch64-linux-gnu/pkgconfig:${sysroot_dir}/usr/lib/pkgconfig:${sysroot_dir}/usr/share/pkgconfig:${sysroot_dir}/usr/local/lib/aarch64-linux-gnu/pkgconfig:${sysroot_dir}/usr/local/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"
if [ -z "${JAVA_HOME:-}" ]; then
arch="$(dpkg --print-architecture 2>/dev/null || true)"
if [ "${arch}" = "amd64" ]; then
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
else
export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-arm64"
fi
else
export JAVA_HOME="${JAVA_HOME}"
fi
git config --global --add safe.directory "${repo_root}"

build_dir="${repo_root}/cmake_build"
cmake_args=(
-B "${build_dir}"
-S "${repo_root}"
-DOpenGL_GL_PREFERENCE=GLVND
-DBUILD_CAMERA_MEME=OFF
)
if [ "${sysroot_mode}" = "full" ]; then
cmake_args+=(
-DCMAKE_SYSROOT="${sysroot_dir}"
-DCMAKE_FIND_ROOT_PATH="${sysroot_dir}"
-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY
-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
-DCMAKE_PREFIX_PATH="${sysroot_dir}/usr;${sysroot_dir}/usr/local"
)
else
cmake_args+=(
-DCMAKE_LIBRARY_PATH="${sysroot_dir}/usr/lib/aarch64-linux-gnu:${sysroot_dir}/usr/local/lib/aarch64-linux-gnu"
-DCMAKE_INCLUDE_PATH="${sysroot_dir}/usr/include:${sysroot_dir}/usr/local/include"
)
fi
cmake "${cmake_args[@]}" ${cmake_extra_args}
cmake --build "${build_dir}" -j"$(nproc)"

cd "${repo_root}"
./gradlew --no-daemon \
-Dmaven.repo.local="${maven_local_repo}" \
build publishToMavenLocal \
-PArchOverride=linuxarm64 \
${gradle_extra_args}
exit 0
fi

docker run --rm --platform="${docker_platform}" \
-v "${repo_root}:/work" \
-v "${sysroot_dir}:/sysroot:ro" \
-v "${maven_local_repo}:/work/.m2-arm64" \
"${docker_image}" bash -lc "
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update >/dev/null
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
cmake \
git \
libdrm-dev \
libegl1-mesa-dev \
libgbm-dev \
libgl1-mesa-dev \
ninja-build \
openjdk-17-jdk \
pkg-config >/dev/null

export PKG_CONFIG_SYSROOT_DIR=/sysroot
export PKG_CONFIG_PATH=/sysroot/usr/lib/aarch64-linux-gnu/pkgconfig:/sysroot/usr/lib/pkgconfig:/sysroot/usr/share/pkgconfig:/sysroot/usr/local/lib/aarch64-linux-gnu/pkgconfig:/sysroot/usr/local/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64
git config --global --add safe.directory /work

cmake_args=(
-B /work/cmake_build
-S /work
-DOpenGL_GL_PREFERENCE=GLVND
-DBUILD_CAMERA_MEME=OFF
)
if [ \"${sysroot_mode}\" = \"full\" ]; then
cmake_args+=(
-DCMAKE_SYSROOT=/sysroot
-DCMAKE_FIND_ROOT_PATH=/sysroot
-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY
-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
-DCMAKE_PREFIX_PATH=/sysroot/usr\\;/sysroot/usr/local
)
else
cmake_args+=(
-DCMAKE_LIBRARY_PATH=/sysroot/usr/lib/aarch64-linux-gnu:/sysroot/usr/local/lib/aarch64-linux-gnu
-DCMAKE_INCLUDE_PATH=/sysroot/usr/include:/sysroot/usr/local/include
)
fi
cmake \${cmake_args[@]} ${cmake_extra_args}
cmake --build /work/cmake_build -j\$(nproc)

cd /work
./gradlew --no-daemon \
-Dmaven.repo.local=/work/.m2-arm64 \
build publishToMavenLocal \
-PArchOverride=linuxarm64 \
${gradle_extra_args}
"
Loading