[v2,12/37] libcamera: software_isp: Start the ISP thread in configure
diff mbox series

Message ID 20250824-b4-v0-5-2-gpuisp-v2-a-v2-12-96f4576c814e@linaro.org
State New
Headers show
Series
  • Add GLES 2.0 GPUISP to libcamera
Related show

Commit Message

Bryan O'Donoghue Aug. 24, 2025, 12:48 a.m. UTC
EGL is not thread-safe and in fact associates invisible handles with the
threadid of the calling context.

As a result we need to make Deabyer::configure() and Debayer::process() in SoftISP execute on
the same thread.

When we call Debayer::configure() in the egl class this will setup and egl context
for us which is associated with the calling thread context. Hence when
Debayer::process(); runs it must run in the same thread as
Debayer::configure() or the hidden Gegl context handles will not point
to the same place.

Move start thread into configure() as a first step towards this.

Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
---
 src/libcamera/software_isp/software_isp.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

Comments

Robert Mader Aug. 29, 2025, 8:50 a.m. UTC | #1
Hi,

On 24.08.25 02:48, Bryan O'Donoghue wrote:
> EGL is not thread-safe and in fact associates invisible handles with the
> threadid of the calling context.
>
> As a result we need to make Deabyer::configure() and Debayer::process() in SoftISP execute on
> the same thread.
>
> When we call Debayer::configure() in the egl class this will setup and egl context
> for us which is associated with the calling thread context. Hence when
> Debayer::process(); runs it must run in the same thread as
> Debayer::configure() or the hidden Gegl context handles will not point
> to the same place.
>
> Move start thread into configure() as a first step towards this.
>
> Signed-off-by: Bryan O'Donoghue<bryan.odonoghue@linaro.org>
> ---
>   src/libcamera/software_isp/software_isp.cpp | 6 +++---
>   1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
> index 28e2a360e7e6d5071e7b97c2bffc60478d245a60..7bee8f0681dd5cedcd81950ed49f611b514e763d 100644
> --- a/src/libcamera/software_isp/software_isp.cpp
> +++ b/src/libcamera/software_isp/software_isp.cpp
> @@ -159,8 +159,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
>   					    metadataReady.emit(frame, metadata);
>   				    });
>   	ipa_->setSensorControls.connect(this, &SoftwareIsp::setSensorCtrls);
> -
> -	debayer_->moveToThread(&ispWorkerThread_);
>   }
>   
>   SoftwareIsp::~SoftwareIsp()
> @@ -262,6 +260,9 @@ int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
>   	if (ret < 0)
>   		return ret;
>   
> +	debayer_->moveToThread(&ispWorkerThread_);

When testing with my OnePlus6 via Pipewire/Snapshot, I reliably hit an 
assert here whenever starting a camera for the second time. I.e. when 
switching to another camera and back, or when closing Snapshot (or some 
other app) and starting it again I get the following crash/bt (every time):

(gdb) bt
#0  __restore_sigs (set=set@entry=0xffff8f465b40) at ./arch/aarch64/syscall_arch.h:48
#1  0x0000ffff92e71e6c in raise (sig=sig@entry=6) at src/signal/raise.c:11
#2  0x0000ffff92e40628 in abort () at src/exit/abort.c:11
#3  0x0000ffff9195c244 in libcamera::LogMessage::~LogMessage (this=0xffff8f465c38) at ../src/libcamera/base/log.cpp:867
#4  0x0000ffff91952348 in libcamera::Object::assertThreadBound (this=this@entry=0xffff8f4266a0, message=message@entry=0xffff91961638 "Object can't be moved from another thread") at ../src/libcamera/base/object.cpp:253
#5  0x0000ffff91952864 in libcamera::Object::moveToThread (this=0xffff8f4266a0, thread=thread@entry=0xffff8f431750) at ../src/libcamera/base/object.cpp:308
#6  0x0000ffff91ab78d4 in libcamera::SoftwareIsp::configure (this=0xffff8f431680, inputCfg=..., outputCfgs=..., configInfo=...) at ../src/libcamera/software_isp/software_isp.cpp:280
#7  0x0000ffff91a93564 in libcamera::SimplePipelineHandler::configure (this=<optimized out>, camera=<optimized out>, c=<optimized out>) at /usr/include/c++/15.2.0/bits/unique_ptr.h:193
#8  0x0000ffff91a17620 in libcamera::BoundMethodArgs<int, libcamera::Camera*, libcamera::CameraConfiguration*>::invokePack<0ul, 1ul> (this=<optimized out>, pack=0xffff85436b20) at ../include/libcamera/base/bound_method.h:100
#9  libcamera::BoundMethodArgs<int, libcamera::Camera*, libcamera::CameraConfiguration*>::invokePack (this=<optimized out>, pack=0xffff85436b20) at ../include/libcamera/base/bound_method.h:111
#10 0x0000ffff91951ff8 in libcamera::Object::message (this=<optimized out>, msg=<optimized out>) at ../src/libcamera/base/object.cpp:211
#11 libcamera::Object::message (this=<optimized out>, msg=<optimized out>) at ../src/libcamera/base/object.cpp:201
#12 0x0000ffff9195e13c in libcamera::Thread::dispatchMessages (this=0xffff91b4fb70, type=type@entry=libcamera::Message::None, receiver=receiver@entry=0x0) at ../src/libcamera/base/thread.cpp:654
#13 0x0000ffff9195625c in libcamera::EventDispatcherPoll::processEvents (this=0xffff91e02780) at ../src/libcamera/base/event_dispatcher_poll.cpp:146
#14 0x0000ffff9195d928 in libcamera::Thread::exec (this=this@entry=0xffff91b4fb70) at ../src/libcamera/base/thread.cpp:311
#15 0x0000ffff91a1a940 in libcamera::CameraManager::Private::run (this=0xffff91b4fb60) at ../src/libcamera/camera_manager.cpp:89
#16 0x0000ffff916f557c in ?? () from /usr/lib/libstdc++.so.6
#17 0x0000ffff92e82534 in start (p=0xffff8f466dd0) at src/thread/pthread_create.c:207
#18 0x0000ffff92e8092c in __clone () at src/thread/aarch64/clone.s:28

and the logs

ERROR Object object.cpp:252 Object can't be moved from another thread
FATAL default object.cpp:253 assertion "false" failed in assertThreadBound()

Given that this is common code this applies independently of whether

LIBCAMERA_SOFTISP_MODE=gpu

is set.

> +	ispWorkerThread_.start();
> +
>   	return debayer_->configure(inputCfg, outputCfgs, ccmEnabled_);
>   }
>   
> @@ -343,7 +344,6 @@ int SoftwareIsp::start()
>   	if (ret)
>   		return ret;
>   
> -	ispWorkerThread_.start();
>   	return 0;
>   }
>   
>
Barnabás Pőcze Aug. 29, 2025, 9:03 a.m. UTC | #2
2025. 08. 29. 10:50 keltezéssel, Robert Mader írta:
> Hi,
> 
> On 24.08.25 02:48, Bryan O'Donoghue wrote:
>> EGL is not thread-safe and in fact associates invisible handles with the
>> threadid of the calling context.
>>
>> As a result we need to make Deabyer::configure() and Debayer::process() in SoftISP execute on
>> the same thread.
>>
>> When we call Debayer::configure() in the egl class this will setup and egl context
>> for us which is associated with the calling thread context. Hence when
>> Debayer::process(); runs it must run in the same thread as
>> Debayer::configure() or the hidden Gegl context handles will not point
>> to the same place.
>>
>> Move start thread into configure() as a first step towards this.
>>
>> Signed-off-by: Bryan O'Donoghue<bryan.odonoghue@linaro.org>
>> ---
>>   src/libcamera/software_isp/software_isp.cpp | 6 +++---
>>   1 file changed, 3 insertions(+), 3 deletions(-)
>>
>> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
>> index 28e2a360e7e6d5071e7b97c2bffc60478d245a60..7bee8f0681dd5cedcd81950ed49f611b514e763d 100644
>> --- a/src/libcamera/software_isp/software_isp.cpp
>> +++ b/src/libcamera/software_isp/software_isp.cpp
>> @@ -159,8 +159,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
>>   					    metadataReady.emit(frame, metadata);
>>   				    });
>>   	ipa_->setSensorControls.connect(this, &SoftwareIsp::setSensorCtrls);
>> -
>> -	debayer_->moveToThread(&ispWorkerThread_);
>>   }
>>
>>   SoftwareIsp::~SoftwareIsp()
>> @@ -262,6 +260,9 @@ int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
>>   	if (ret < 0)
>>   		return ret;
>>
>> +	debayer_->moveToThread(&ispWorkerThread_);
> 
> When testing with my OnePlus6 via Pipewire/Snapshot, I reliably hit an assert here whenever starting a camera for the second time. I.e. when switching to another camera and back, or when closing Snapshot (or some other app) and starting it again I get the following crash/bt (every time):
> 
> (gdb) bt
> #0  __restore_sigs (set=set@entry=0xffff8f465b40) at ./arch/aarch64/syscall_arch.h:48
> #1  0x0000ffff92e71e6c in raise (sig=sig@entry=6) at src/signal/raise.c:11
> #2  0x0000ffff92e40628 in abort () at src/exit/abort.c:11
> #3  0x0000ffff9195c244 in libcamera::LogMessage::~LogMessage (this=0xffff8f465c38) at ../src/libcamera/base/log.cpp:867
> #4  0x0000ffff91952348 in libcamera::Object::assertThreadBound (this=this@entry=0xffff8f4266a0, message=message@entry=0xffff91961638 "Object can't be moved from another thread") at ../src/libcamera/base/object.cpp:253
> #5  0x0000ffff91952864 in libcamera::Object::moveToThread (this=0xffff8f4266a0, thread=thread@entry=0xffff8f431750) at ../src/libcamera/base/object.cpp:308
> #6  0x0000ffff91ab78d4 in libcamera::SoftwareIsp::configure (this=0xffff8f431680, inputCfg=..., outputCfgs=..., configInfo=...) at ../src/libcamera/software_isp/software_isp.cpp:280
> #7  0x0000ffff91a93564 in libcamera::SimplePipelineHandler::configure (this=<optimized out>, camera=<optimized out>, c=<optimized out>) at /usr/include/c++/15.2.0/bits/unique_ptr.h:193
> #8  0x0000ffff91a17620 in libcamera::BoundMethodArgs<int, libcamera::Camera*, libcamera::CameraConfiguration*>::invokePack<0ul, 1ul> (this=<optimized out>, pack=0xffff85436b20) at ../include/libcamera/base/bound_method.h:100
> #9  libcamera::BoundMethodArgs<int, libcamera::Camera*, libcamera::CameraConfiguration*>::invokePack (this=<optimized out>, pack=0xffff85436b20) at ../include/libcamera/base/bound_method.h:111
> #10 0x0000ffff91951ff8 in libcamera::Object::message (this=<optimized out>, msg=<optimized out>) at ../src/libcamera/base/object.cpp:211
> #11 libcamera::Object::message (this=<optimized out>, msg=<optimized out>) at ../src/libcamera/base/object.cpp:201
> #12 0x0000ffff9195e13c in libcamera::Thread::dispatchMessages (this=0xffff91b4fb70, type=type@entry=libcamera::Message::None, receiver=receiver@entry=0x0) at ../src/libcamera/base/thread.cpp:654
> #13 0x0000ffff9195625c in libcamera::EventDispatcherPoll::processEvents (this=0xffff91e02780) at ../src/libcamera/base/event_dispatcher_poll.cpp:146
> #14 0x0000ffff9195d928 in libcamera::Thread::exec (this=this@entry=0xffff91b4fb70) at ../src/libcamera/base/thread.cpp:311
> #15 0x0000ffff91a1a940 in libcamera::CameraManager::Private::run (this=0xffff91b4fb60) at ../src/libcamera/camera_manager.cpp:89
> #16 0x0000ffff916f557c in ?? () from /usr/lib/libstdc++.so.6
> #17 0x0000ffff92e82534 in start (p=0xffff8f466dd0) at src/thread/pthread_create.c:207
> #18 0x0000ffff92e8092c in __clone () at src/thread/aarch64/clone.s:28
> 
> and the logs
> 
> ERROR Object object.cpp:252 Object can't be moved from another thread
> FATAL default object.cpp:253 assertion "false" failed in assertThreadBound()

A thread can only be moved from its current thread. So the when this code runs
the second time, `*debayer_` object is already on `ispWorkerThread_`, which is
not the current thread, hence the assertion.

It's not clear to me why moving the object in the constructor is not sufficient, I think
just adding `ispWorkerThread_.start()` in `SoftwareIsp::configure()` should be sufficient.
Removing it from `SoftwareIsp::start()` is also not quite right because then a
configure-start-stop-start-stop sequence won't work as expected as far as I can tell.
Or maybe the thread can be started in the constructor and kept running.


Regards,
Barnabás Pőcze


> 
> Given that this is common code this applies independently of whether
> 
> LIBCAMERA_SOFTISP_MODE=gpu
> 
> is set.
> 
>> +	ispWorkerThread_.start();
>> +
>>   	return debayer_->configure(inputCfg, outputCfgs, ccmEnabled_);
>>   }
>>
>> @@ -343,7 +344,6 @@ int SoftwareIsp::start()
>>   	if (ret)
>>   		return ret;
>>
>> -	ispWorkerThread_.start();
>>   	return 0;
>>   }
>>
>>
> --
> Robert Mader
> Consultant Software Developer
> 
> Collabora Ltd.
> Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
> Registered in England & Wales, no. 5513718
>
Robert Mader Sept. 11, 2025, 1:09 p.m. UTC | #3
On 29.08.25 11:03, Barnabás Pőcze wrote:
> 2025. 08. 29. 10:50 keltezéssel, Robert Mader írta:
>> Hi,
>>
>> On 24.08.25 02:48, Bryan O'Donoghue wrote:
>>> EGL is not thread-safe and in fact associates invisible handles with 
>>> the
>>> threadid of the calling context.
>>>
>>> As a result we need to make Deabyer::configure() and 
>>> Debayer::process() in SoftISP execute on
>>> the same thread.
>>>
>>> When we call Debayer::configure() in the egl class this will setup 
>>> and egl context
>>> for us which is associated with the calling thread context. Hence when
>>> Debayer::process(); runs it must run in the same thread as
>>> Debayer::configure() or the hidden Gegl context handles will not point
>>> to the same place.
>>>
>>> Move start thread into configure() as a first step towards this.
>>>
>>> Signed-off-by: Bryan O'Donoghue<bryan.odonoghue@linaro.org>
>>> ---
>>>   src/libcamera/software_isp/software_isp.cpp | 6 +++---
>>>   1 file changed, 3 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/src/libcamera/software_isp/software_isp.cpp 
>>> b/src/libcamera/software_isp/software_isp.cpp
>>> index 
>>> 28e2a360e7e6d5071e7b97c2bffc60478d245a60..7bee8f0681dd5cedcd81950ed49f611b514e763d 
>>> 100644
>>> --- a/src/libcamera/software_isp/software_isp.cpp
>>> +++ b/src/libcamera/software_isp/software_isp.cpp
>>> @@ -159,8 +159,6 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, 
>>> const CameraSensor *sensor,
>>>                           metadataReady.emit(frame, metadata);
>>>                       });
>>>       ipa_->setSensorControls.connect(this, 
>>> &SoftwareIsp::setSensorCtrls);
>>> -
>>> -    debayer_->moveToThread(&ispWorkerThread_);
>>>   }
>>>
>>>   SoftwareIsp::~SoftwareIsp()
>>> @@ -262,6 +260,9 @@ int SoftwareIsp::configure(const 
>>> StreamConfiguration &inputCfg,
>>>       if (ret < 0)
>>>           return ret;
>>>
>>> +    debayer_->moveToThread(&ispWorkerThread_);
>>
>> When testing with my OnePlus6 via Pipewire/Snapshot, I reliably hit 
>> an assert here whenever starting a camera for the second time. I.e. 
>> when switching to another camera and back, or when closing Snapshot 
>> (or some other app) and starting it again I get the following 
>> crash/bt (every time):
>>
>> (gdb) bt
>> #0  __restore_sigs (set=set@entry=0xffff8f465b40) at 
>> ./arch/aarch64/syscall_arch.h:48
>> #1  0x0000ffff92e71e6c in raise (sig=sig@entry=6) at 
>> src/signal/raise.c:11
>> #2  0x0000ffff92e40628 in abort () at src/exit/abort.c:11
>> #3  0x0000ffff9195c244 in libcamera::LogMessage::~LogMessage 
>> (this=0xffff8f465c38) at ../src/libcamera/base/log.cpp:867
>> #4  0x0000ffff91952348 in libcamera::Object::assertThreadBound 
>> (this=this@entry=0xffff8f4266a0, message=message@entry=0xffff91961638 
>> "Object can't be moved from another thread") at 
>> ../src/libcamera/base/object.cpp:253
>> #5  0x0000ffff91952864 in libcamera::Object::moveToThread 
>> (this=0xffff8f4266a0, thread=thread@entry=0xffff8f431750) at 
>> ../src/libcamera/base/object.cpp:308
>> #6  0x0000ffff91ab78d4 in libcamera::SoftwareIsp::configure 
>> (this=0xffff8f431680, inputCfg=..., outputCfgs=..., configInfo=...) 
>> at ../src/libcamera/software_isp/software_isp.cpp:280
>> #7  0x0000ffff91a93564 in libcamera::SimplePipelineHandler::configure 
>> (this=<optimized out>, camera=<optimized out>, c=<optimized out>) at 
>> /usr/include/c++/15.2.0/bits/unique_ptr.h:193
>> #8  0x0000ffff91a17620 in libcamera::BoundMethodArgs<int, 
>> libcamera::Camera*, libcamera::CameraConfiguration*>::invokePack<0ul, 
>> 1ul> (this=<optimized out>, pack=0xffff85436b20) at 
>> ../include/libcamera/base/bound_method.h:100
>> #9  libcamera::BoundMethodArgs<int, libcamera::Camera*, 
>> libcamera::CameraConfiguration*>::invokePack (this=<optimized out>, 
>> pack=0xffff85436b20) at ../include/libcamera/base/bound_method.h:111
>> #10 0x0000ffff91951ff8 in libcamera::Object::message (this=<optimized 
>> out>, msg=<optimized out>) at ../src/libcamera/base/object.cpp:211
>> #11 libcamera::Object::message (this=<optimized out>, msg=<optimized 
>> out>) at ../src/libcamera/base/object.cpp:201
>> #12 0x0000ffff9195e13c in libcamera::Thread::dispatchMessages 
>> (this=0xffff91b4fb70, type=type@entry=libcamera::Message::None, 
>> receiver=receiver@entry=0x0) at ../src/libcamera/base/thread.cpp:654
>> #13 0x0000ffff9195625c in 
>> libcamera::EventDispatcherPoll::processEvents (this=0xffff91e02780) 
>> at ../src/libcamera/base/event_dispatcher_poll.cpp:146
>> #14 0x0000ffff9195d928 in libcamera::Thread::exec 
>> (this=this@entry=0xffff91b4fb70) at ../src/libcamera/base/thread.cpp:311
>> #15 0x0000ffff91a1a940 in libcamera::CameraManager::Private::run 
>> (this=0xffff91b4fb60) at ../src/libcamera/camera_manager.cpp:89
>> #16 0x0000ffff916f557c in ?? () from /usr/lib/libstdc++.so.6
>> #17 0x0000ffff92e82534 in start (p=0xffff8f466dd0) at 
>> src/thread/pthread_create.c:207
>> #18 0x0000ffff92e8092c in __clone () at src/thread/aarch64/clone.s:28
>>
>> and the logs
>>
>> ERROR Object object.cpp:252 Object can't be moved from another thread
>> FATAL default object.cpp:253 assertion "false" failed in 
>> assertThreadBound()
>
> A thread can only be moved from its current thread. So the when this 
> code runs
> the second time, `*debayer_` object is already on `ispWorkerThread_`, 
> which is
> not the current thread, hence the assertion.
>
> It's not clear to me why moving the object in the constructor is not 
> sufficient, I think
> just adding `ispWorkerThread_.start()` in `SoftwareIsp::configure()` 
> should be sufficient.
> Removing it from `SoftwareIsp::start()` is also not quite right 
> because then a
> configure-start-stop-start-stop sequence won't work as expected as far 
> as I can tell.
> Or maybe the thread can be started in the constructor and kept running.

I had another look and can confirm that moving

debayer_->moveToThread(&ispWorkerThread_);

back to the constructor (leaving `ispWorkerThread_.start();` and the 
later added `debayer_->invokeMethod()` in place) solves the issue here 
and makes the branch run pretty robust for me.

Regarding the described sequence: I guess another option could be to 
call `ispWorkerThread_.start();` in both `configure()` and `start()` as 
repeated calls get ignored. IIUC that should only have negative 
consequences - in the form of minor wasted resources -  if a successfull 
`configure()` is never followed up with a `start()`.

P.S.: I added a fixup commit to 
https://gitlab.freedesktop.org/rmader/libcamera/-/commits/gpuisp-v2-buffer-import 
in case anybody wants to try this with PW.

P.S.S.: on a Librem5 - when combined with upcoming PW, GTK, Snapshot, 
Mesa and forcing on GLES 3.0 - this can almost rival Millipixels 
performance wise already - really really cool!

Regards,

Robert

>
> Regards,
> Barnabás Pőcze
>
>
>>
>> Given that this is common code this applies independently of whether
>>
>> LIBCAMERA_SOFTISP_MODE=gpu
>>
>> is set.
>>
>>> +    ispWorkerThread_.start();
>>> +
>>>       return debayer_->configure(inputCfg, outputCfgs, ccmEnabled_);
>>>   }
>>>
>>> @@ -343,7 +344,6 @@ int SoftwareIsp::start()
>>>       if (ret)
>>>           return ret;
>>>
>>> -    ispWorkerThread_.start();
>>>       return 0;
>>>   }
>>>
>>>
>> -- 
>> Robert Mader
>> Consultant Software Developer
>>
>> Collabora Ltd.
>> Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
>> Registered in England & Wales, no. 5513718
>>
>

Patch
diff mbox series

diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 28e2a360e7e6d5071e7b97c2bffc60478d245a60..7bee8f0681dd5cedcd81950ed49f611b514e763d 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -159,8 +159,6 @@  SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
 					    metadataReady.emit(frame, metadata);
 				    });
 	ipa_->setSensorControls.connect(this, &SoftwareIsp::setSensorCtrls);
-
-	debayer_->moveToThread(&ispWorkerThread_);
 }
 
 SoftwareIsp::~SoftwareIsp()
@@ -262,6 +260,9 @@  int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
 	if (ret < 0)
 		return ret;
 
+	debayer_->moveToThread(&ispWorkerThread_);
+	ispWorkerThread_.start();
+
 	return debayer_->configure(inputCfg, outputCfgs, ccmEnabled_);
 }
 
@@ -343,7 +344,6 @@  int SoftwareIsp::start()
 	if (ret)
 		return ret;
 
-	ispWorkerThread_.start();
 	return 0;
 }