1use edgefirst_decoder::{DetectBox, ProtoData, Segmentation};
5use std::panic::AssertUnwindSafe;
6use std::ptr::NonNull;
7use std::thread::JoinHandle;
8use tokio::sync::mpsc::{Sender, WeakSender};
9
10use super::processor::GLProcessorST;
11use super::shaders::check_gl_error;
12use super::{EglDisplayKind, Int8InterpolationMode, TransferBackend};
13use crate::{Crop, Error, Flip, ImageProcessorTrait, Rotation};
14use edgefirst_tensor::TensorDyn;
15
16#[allow(clippy::type_complexity)]
17enum GLProcessorMessage {
18 ImageConvert(
19 SendablePtr<TensorDyn>,
20 SendablePtr<TensorDyn>,
21 Rotation,
22 Flip,
23 Crop,
24 bool, tokio::sync::oneshot::Sender<Result<(), Error>>,
26 ),
27 Flush(tokio::sync::oneshot::Sender<Result<(), Error>>),
29 SetColors(
30 Vec<[u8; 4]>,
31 tokio::sync::oneshot::Sender<Result<(), Error>>,
32 ),
33 DrawDecodedMasks(
34 SendablePtr<TensorDyn>,
35 SendablePtr<DetectBox>,
36 SendablePtr<Segmentation>,
37 f32, Option<SendablePtr<TensorDyn>>, Option<[f32; 4]>, crate::ColorMode,
41 tokio::sync::oneshot::Sender<Result<(), Error>>,
42 ),
43 DrawProtoMasks(
44 SendablePtr<TensorDyn>,
45 SendablePtr<DetectBox>,
46 SendablePtr<ProtoData>,
47 f32, Option<SendablePtr<TensorDyn>>, Option<[f32; 4]>, crate::ColorMode,
51 tokio::sync::oneshot::Sender<Result<(), Error>>,
52 ),
53 SetInt8Interpolation(
54 Int8InterpolationMode,
55 tokio::sync::oneshot::Sender<Result<(), Error>>,
56 ),
57 SetColorimetryMode(
59 crate::ColorimetryMode,
60 tokio::sync::oneshot::Sender<Result<(), Error>>,
61 ),
62 EglCacheStats(tokio::sync::oneshot::Sender<Result<super::cache::GlCacheStats, Error>>),
64 PboCreate(
65 usize, tokio::sync::oneshot::Sender<Result<u32, Error>>,
67 ),
68 PboMap(
69 u32, usize, tokio::sync::oneshot::Sender<Result<edgefirst_tensor::PboMapping, Error>>,
72 ),
73 PboUnmap(
74 u32, tokio::sync::oneshot::Sender<Result<(), Error>>,
76 ),
77 PboDelete(u32), CudaRegisterBuffer(u32, tokio::sync::oneshot::Sender<Option<usize>>),
79 CudaMap(usize, tokio::sync::oneshot::Sender<Option<(usize, usize)>>),
80 CudaUnmap(usize), CudaUnregister(usize), }
83
84fn pbo_elem_count(
94 width: usize,
95 height: usize,
96 format: edgefirst_tensor::PixelFormat,
97) -> Option<usize> {
98 format
105 .image_shape(width, height)?
106 .iter()
107 .try_fold(1usize, |acc, &d| acc.checked_mul(d))
108}
109
110fn pbo_shape(width: usize, height: usize, format: edgefirst_tensor::PixelFormat) -> Vec<usize> {
114 format
115 .image_shape(width, height)
116 .unwrap_or_else(|| vec![height, width, format.channels()])
117}
118
119fn register_pbo_cuda<T>(
128 tensor: &mut edgefirst_tensor::Tensor<T>,
129 buffer_id: u32,
130 size: usize,
131 sender: &Sender<GLProcessorMessage>,
132) where
133 T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync,
134{
135 if !edgefirst_tensor::is_cuda_available() {
136 return;
137 }
138 let (tx, rx) = tokio::sync::oneshot::channel();
139 if sender
140 .blocking_send(GLProcessorMessage::CudaRegisterBuffer(buffer_id, tx))
141 .is_ok()
142 {
143 if let Ok(Some(resource)) = rx.blocking_recv() {
144 let ops = std::sync::Arc::new(GlCudaOps {
145 sender: sender.downgrade(),
146 });
147 tensor.set_cuda_handle(edgefirst_tensor::CudaHandle::new_gl(
148 resource as *mut std::ffi::c_void,
149 size,
150 ops,
151 ));
152 }
153 }
154}
155
156struct GlPboOps {
163 sender: WeakSender<GLProcessorMessage>,
164}
165
166unsafe impl edgefirst_tensor::PboOps for GlPboOps {
171 fn map_buffer(
172 &self,
173 buffer_id: u32,
174 size: usize,
175 ) -> edgefirst_tensor::Result<edgefirst_tensor::PboMapping> {
176 let sender = self
177 .sender
178 .upgrade()
179 .ok_or(edgefirst_tensor::Error::PboDisconnected)?;
180 let (tx, rx) = tokio::sync::oneshot::channel();
181 sender
182 .blocking_send(GLProcessorMessage::PboMap(buffer_id, size, tx))
183 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?;
184 rx.blocking_recv()
185 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?
186 .map_err(|e| {
187 edgefirst_tensor::Error::NotImplemented(format!("GL PBO map failed: {e:?}"))
188 })
189 }
190
191 fn unmap_buffer(&self, buffer_id: u32) -> edgefirst_tensor::Result<()> {
192 let sender = self
193 .sender
194 .upgrade()
195 .ok_or(edgefirst_tensor::Error::PboDisconnected)?;
196 let (tx, rx) = tokio::sync::oneshot::channel();
197 sender
198 .blocking_send(GLProcessorMessage::PboUnmap(buffer_id, tx))
199 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?;
200 rx.blocking_recv()
201 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?
202 .map_err(|e| {
203 edgefirst_tensor::Error::NotImplemented(format!("GL PBO unmap failed: {e:?}"))
204 })
205 }
206
207 fn delete_buffer(&self, buffer_id: u32) {
208 if let Some(sender) = self.sender.upgrade() {
209 let _ = sender.blocking_send(GLProcessorMessage::PboDelete(buffer_id));
210 }
211 }
212}
213
214struct GlCudaOps {
221 sender: WeakSender<GLProcessorMessage>,
222}
223
224impl edgefirst_tensor::CudaGlOps for GlCudaOps {
225 fn map(&self, resource: *mut std::ffi::c_void) -> Option<(*mut std::ffi::c_void, usize)> {
226 let sender = self.sender.upgrade()?;
227 let (tx, rx) = tokio::sync::oneshot::channel();
228 sender
229 .blocking_send(GLProcessorMessage::CudaMap(resource as usize, tx))
230 .ok()?;
231 rx.blocking_recv()
232 .ok()?
233 .map(|(p, l)| (p as *mut std::ffi::c_void, l))
234 }
235
236 fn unmap(&self, resource: *mut std::ffi::c_void) {
237 if let Some(sender) = self.sender.upgrade() {
238 let _ = sender.blocking_send(GLProcessorMessage::CudaUnmap(resource as usize));
239 }
240 }
241
242 fn unregister(&self, resource: *mut std::ffi::c_void) {
243 if let Some(sender) = self.sender.upgrade() {
244 let _ = sender.blocking_send(GLProcessorMessage::CudaUnregister(resource as usize));
245 }
246 }
247}
248
249#[derive(Debug)]
254pub struct GLProcessorThreaded {
255 handle: Option<JoinHandle<()>>,
257
258 sender: Option<Sender<GLProcessorMessage>>,
260 caps: super::platform::PlatformCaps,
264}
265
266unsafe impl Send for GLProcessorThreaded {}
267unsafe impl Sync for GLProcessorThreaded {}
268
269struct SendablePtr<T: Send> {
270 ptr: NonNull<T>,
271 len: usize,
272}
273
274unsafe impl<T> Send for SendablePtr<T> where T: Send {}
275
276fn panic_message(info: &(dyn std::any::Any + Send)) -> String {
278 if let Some(s) = info.downcast_ref::<&str>() {
279 s.to_string()
280 } else if let Some(s) = info.downcast_ref::<String>() {
281 s.clone()
282 } else {
283 "unknown panic".to_string()
284 }
285}
286
287impl GLProcessorThreaded {
288 pub fn new(kind: Option<EglDisplayKind>) -> Result<Self, Error> {
290 let (send, mut recv) = tokio::sync::mpsc::channel::<GLProcessorMessage>(1);
291
292 let (create_ctx_send, create_ctx_recv) = tokio::sync::oneshot::channel();
293
294 let func = move || {
295 #[cfg(target_os = "linux")]
300 let init_result = {
301 let _guard = super::context::GL_MUTEX
302 .lock()
303 .unwrap_or_else(|e| e.into_inner());
304 GLProcessorST::new(kind)
305 };
306 #[cfg(target_os = "macos")]
307 let init_result = GLProcessorST::new(kind);
308 let mut gl_converter = match init_result {
309 Ok(gl) => gl,
310 Err(e) => {
311 let _ = create_ctx_send.send(Err(e));
312 return;
313 }
314 };
315 let caps = gl_converter.platform_caps();
318 let _ = create_ctx_send.send(Ok(caps));
319 let mut poisoned = false;
320 let serialize_per_msg = match std::env::var("EDGEFIRST_GL_SERIALIZE").as_deref() {
329 Ok("full") => true,
330 Ok("lifecycle") => false,
331 _ => caps.serialize_gl,
332 };
333 log::debug!(
334 "GL serialization policy: {}",
335 if serialize_per_msg {
336 "Full (per-message global lock)"
337 } else {
338 "LifecycleOnly (parallel across processors)"
339 }
340 );
341 while let Some(msg) = recv.blocking_recv() {
342 #[cfg(target_os = "linux")]
351 let _guard = serialize_per_msg.then(|| {
352 super::context::GL_MUTEX
353 .lock()
354 .unwrap_or_else(|e| e.into_inner())
355 });
356 #[cfg(target_os = "macos")]
357 let _guard = serialize_per_msg.then(|| {
358 static MSG_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
359 MSG_MUTEX.lock().unwrap_or_else(|e| e.into_inner())
360 });
361
362 if poisoned {
367 let poison_err = crate::Error::Internal(
368 "GL context is poisoned after a prior panic".to_string(),
369 );
370 match msg {
371 GLProcessorMessage::ImageConvert(.., resp) => {
372 let _ = resp.send(Err(poison_err));
373 }
374 GLProcessorMessage::Flush(resp) => {
375 let _ = resp.send(Err(poison_err));
376 }
377 GLProcessorMessage::DrawDecodedMasks(.., resp) => {
378 let _ = resp.send(Err(poison_err));
379 }
380 GLProcessorMessage::DrawProtoMasks(.., resp) => {
381 let _ = resp.send(Err(poison_err));
382 }
383 GLProcessorMessage::SetColors(_, resp) => {
384 let _ = resp.send(Err(poison_err));
385 }
386 GLProcessorMessage::SetInt8Interpolation(_, resp) => {
387 let _ = resp.send(Err(poison_err));
388 }
389 GLProcessorMessage::SetColorimetryMode(_, resp) => {
390 let _ = resp.send(Err(poison_err));
391 }
392 GLProcessorMessage::EglCacheStats(resp) => {
393 let _ = resp.send(Err(poison_err));
394 }
395 GLProcessorMessage::PboCreate(_, resp) => {
396 let _ = resp.send(Err(poison_err));
397 }
398 GLProcessorMessage::PboMap(_, _, resp) => {
399 let _ = resp.send(Err(poison_err));
400 }
401 GLProcessorMessage::PboUnmap(_, resp) => {
402 let _ = resp.send(Err(poison_err));
403 }
404 GLProcessorMessage::PboDelete(_) => {}
405 GLProcessorMessage::CudaRegisterBuffer(_, resp) => {
406 let _ = resp.send(None);
407 }
408 GLProcessorMessage::CudaMap(_, resp) => {
409 let _ = resp.send(None);
410 }
411 GLProcessorMessage::CudaUnmap(_) => {}
415 GLProcessorMessage::CudaUnregister(_) => {}
416 }
417 continue;
418 }
419
420 match msg {
421 GLProcessorMessage::ImageConvert(
422 src,
423 mut dst,
424 rotation,
425 flip,
426 crop,
427 defer,
428 resp,
429 ) => {
430 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
433 let src = unsafe { src.ptr.as_ref() };
434 let dst = unsafe { dst.ptr.as_mut() };
435 if defer {
439 gl_converter.convert_deferred(src, dst, rotation, flip, crop)
440 } else {
441 gl_converter.convert(src, dst, rotation, flip, crop)
442 }
443 }));
444 let _ = resp.send(match result {
445 Ok(res) => res,
446 Err(e) => {
447 poisoned = true;
448 Err(crate::Error::Internal(format!(
449 "GL thread panicked during ImageConvert: {}",
450 panic_message(e.as_ref()),
451 )))
452 }
453 });
454 }
455 GLProcessorMessage::Flush(resp) => {
456 let result =
457 std::panic::catch_unwind(AssertUnwindSafe(|| gl_converter.flush()));
458 let _ = resp.send(match result {
459 Ok(res) => res,
460 Err(e) => {
461 poisoned = true;
462 Err(crate::Error::Internal(format!(
463 "GL thread panicked during Flush: {}",
464 panic_message(e.as_ref()),
465 )))
466 }
467 });
468 }
469 GLProcessorMessage::DrawDecodedMasks(
470 mut dst,
471 det,
472 seg,
473 opacity,
474 bg,
475 letterbox,
476 color_mode,
477 resp,
478 ) => {
479 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
483 let dst = unsafe { dst.ptr.as_mut() };
484 let det =
485 unsafe { std::slice::from_raw_parts(det.ptr.as_ptr(), det.len) };
486 let seg =
487 unsafe { std::slice::from_raw_parts(seg.ptr.as_ptr(), seg.len) };
488 let bg_ref = bg.map(|p| unsafe { &*p.ptr.as_ptr() });
489 gl_converter.draw_decoded_masks(
490 dst,
491 det,
492 seg,
493 crate::MaskOverlay {
494 background: bg_ref,
495 opacity,
496 letterbox,
497 color_mode,
498 },
499 )
500 }));
501 let _ = resp.send(match result {
502 Ok(res) => res,
503 Err(e) => {
504 poisoned = true;
505 Err(crate::Error::Internal(format!(
506 "GL thread panicked during DrawDecodedMasks: {}",
507 panic_message(e.as_ref()),
508 )))
509 }
510 });
511 }
512 GLProcessorMessage::DrawProtoMasks(
513 mut dst,
514 det,
515 proto_data,
516 opacity,
517 bg,
518 letterbox,
519 color_mode,
520 resp,
521 ) => {
522 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
525 let dst = unsafe { dst.ptr.as_mut() };
526 let det =
527 unsafe { std::slice::from_raw_parts(det.ptr.as_ptr(), det.len) };
528 let bg_ref = bg.map(|p| unsafe { &*p.ptr.as_ptr() });
529 let proto_data = unsafe { proto_data.ptr.as_ref() };
530 gl_converter.draw_proto_masks(
531 dst,
532 det,
533 proto_data,
534 crate::MaskOverlay {
535 background: bg_ref,
536 opacity,
537 letterbox,
538 color_mode,
539 },
540 )
541 }));
542 let _ = resp.send(match result {
543 Ok(res) => res,
544 Err(e) => {
545 poisoned = true;
546 Err(crate::Error::Internal(format!(
547 "GL thread panicked during DrawProtoMasks: {}",
548 panic_message(e.as_ref()),
549 )))
550 }
551 });
552 }
553 GLProcessorMessage::SetColors(colors, resp) => {
554 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
555 gl_converter.set_class_colors(&colors)
556 }));
557 let _ = resp.send(match result {
558 Ok(res) => res,
559 Err(e) => {
560 poisoned = true;
561 Err(crate::Error::Internal(format!(
562 "GL thread panicked during SetColors: {}",
563 panic_message(e.as_ref()),
564 )))
565 }
566 });
567 }
568 GLProcessorMessage::SetColorimetryMode(mode, resp) => {
569 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
570 gl_converter.set_colorimetry_mode(mode);
571 Ok(())
572 }));
573 let _ = resp.send(match result {
574 Ok(res) => res,
575 Err(e) => {
576 poisoned = true;
577 Err(crate::Error::Internal(format!(
578 "GL thread panicked during SetColorimetryMode: {}",
579 panic_message(e.as_ref()),
580 )))
581 }
582 });
583 }
584 GLProcessorMessage::SetInt8Interpolation(mode, resp) => {
585 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
586 gl_converter.set_int8_interpolation_mode(mode);
587 Ok(())
588 }));
589 let _ = resp.send(match result {
590 Ok(res) => res,
591 Err(e) => {
592 poisoned = true;
593 Err(crate::Error::Internal(format!(
594 "GL thread panicked during SetInt8Interpolation: {}",
595 panic_message(e.as_ref()),
596 )))
597 }
598 });
599 }
600 GLProcessorMessage::EglCacheStats(resp) => {
601 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
602 Ok(gl_converter.egl_cache_stats())
603 }));
604 let _ = resp.send(match result {
605 Ok(res) => res,
606 Err(e) => {
607 poisoned = true;
608 Err(crate::Error::Internal(format!(
609 "GL thread panicked during EglCacheStats: {}",
610 panic_message(e.as_ref()),
611 )))
612 }
613 });
614 }
615 GLProcessorMessage::PboCreate(size, resp) => {
616 let result = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
617 let mut id: u32 = 0;
618 edgefirst_gl::gl::GenBuffers(1, &mut id);
619 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, id);
620 edgefirst_gl::gl::BufferData(
621 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
622 size as isize,
623 std::ptr::null(),
624 edgefirst_gl::gl::STREAM_COPY,
625 );
626 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
627 match check_gl_error("PboCreate", 0) {
628 Ok(()) => Ok(id),
629 Err(e) => {
630 edgefirst_gl::gl::DeleteBuffers(1, &id);
631 Err(e)
632 }
633 }
634 }));
635 let _ = resp.send(match result {
636 Ok(res) => res,
637 Err(e) => {
638 poisoned = true;
639 Err(crate::Error::Internal(format!(
640 "GL thread panicked during PboCreate: {}",
641 panic_message(e.as_ref()),
642 )))
643 }
644 });
645 }
646 GLProcessorMessage::PboMap(buffer_id, size, resp) => {
647 let result = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
648 edgefirst_gl::gl::BindBuffer(
649 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
650 buffer_id,
651 );
652 let ptr = edgefirst_gl::gl::MapBufferRange(
653 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
654 0,
655 size as isize,
656 edgefirst_gl::gl::MAP_READ_BIT | edgefirst_gl::gl::MAP_WRITE_BIT,
657 );
658 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
659 if ptr.is_null() {
660 Err(crate::Error::OpenGl(
661 "glMapBufferRange returned null".to_string(),
662 ))
663 } else {
664 Ok(edgefirst_tensor::PboMapping {
665 ptr: ptr as *mut u8,
666 size,
667 })
668 }
669 }));
670 let _ = resp.send(match result {
671 Ok(res) => res,
672 Err(e) => {
673 poisoned = true;
674 Err(crate::Error::Internal(format!(
675 "GL thread panicked during PboMap: {}",
676 panic_message(e.as_ref()),
677 )))
678 }
679 });
680 }
681 GLProcessorMessage::PboUnmap(buffer_id, resp) => {
682 let result = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
683 edgefirst_gl::gl::BindBuffer(
684 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
685 buffer_id,
686 );
687 let ok =
688 edgefirst_gl::gl::UnmapBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER);
689 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
690 if ok == edgefirst_gl::gl::FALSE {
691 Err(Error::OpenGl(
692 "PBO data was corrupted during mapping".into(),
693 ))
694 } else {
695 check_gl_error("PboUnmap", 0)
696 }
697 }));
698 let _ = resp.send(match result {
699 Ok(res) => res,
700 Err(e) => {
701 poisoned = true;
702 Err(crate::Error::Internal(format!(
703 "GL thread panicked during PboUnmap: {}",
704 panic_message(e.as_ref()),
705 )))
706 }
707 });
708 }
709 GLProcessorMessage::PboDelete(buffer_id) => {
710 if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
711 edgefirst_gl::gl::DeleteBuffers(1, &buffer_id);
712 })) {
713 poisoned = true;
714 log::error!(
715 "GL thread panicked during PboDelete: {}",
716 panic_message(e.as_ref()),
717 );
718 }
719 }
720 GLProcessorMessage::CudaRegisterBuffer(buffer_id, resp) => {
721 let _ = resp.send(edgefirst_tensor::gl_register_buffer(buffer_id));
723 }
724 GLProcessorMessage::CudaMap(resource, resp) => {
725 gl_converter.flush_pending();
732 let _ = resp.send(edgefirst_tensor::gl_map_resource(resource));
733 }
734 GLProcessorMessage::CudaUnmap(resource) => {
735 edgefirst_tensor::gl_unmap_resource(resource);
736 }
737 GLProcessorMessage::CudaUnregister(resource) => {
738 edgefirst_tensor::gl_unregister_resource(resource);
739 }
740 }
741 gl_converter.end_gpu_pass_if_synced();
745 }
746 #[cfg(target_os = "linux")]
749 let _guard = super::context::GL_MUTEX
750 .lock()
751 .unwrap_or_else(|e| e.into_inner());
752 drop(gl_converter);
753 };
754
755 let handle = std::thread::spawn(func);
757
758 let caps = match create_ctx_recv.blocking_recv() {
759 Ok(Err(e)) => return Err(e),
760 Err(_) => {
761 return Err(Error::Internal(
762 "GL converter error messaging closed without update".to_string(),
763 ));
764 }
765 Ok(Ok(caps)) => caps,
766 };
767
768 Ok(Self {
769 handle: Some(handle),
770 sender: Some(send),
771 caps,
772 })
773 }
774}
775
776impl ImageProcessorTrait for GLProcessorThreaded {
777 fn convert(
778 &mut self,
779 src: &TensorDyn,
780 dst: &mut TensorDyn,
781 rotation: crate::Rotation,
782 flip: Flip,
783 crop: Crop,
784 ) -> crate::Result<()> {
785 let (err_send, err_recv) = tokio::sync::oneshot::channel();
786 self.sender
787 .as_ref()
788 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
789 .blocking_send(GLProcessorMessage::ImageConvert(
790 SendablePtr {
791 ptr: NonNull::from(src),
792 len: 1,
793 },
794 SendablePtr {
795 ptr: NonNull::from(dst),
796 len: 1,
797 },
798 rotation,
799 flip,
800 crop,
801 false, err_send,
803 ))
804 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
805 err_recv.blocking_recv().map_err(|_| {
806 Error::Internal("GL converter error messaging closed without update".to_string())
807 })?
808 }
809
810 fn convert_deferred(
811 &mut self,
812 src: &TensorDyn,
813 dst: &mut TensorDyn,
814 rotation: crate::Rotation,
815 flip: Flip,
816 crop: Crop,
817 ) -> crate::Result<()> {
818 let (err_send, err_recv) = tokio::sync::oneshot::channel();
819 self.sender
820 .as_ref()
821 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
822 .blocking_send(GLProcessorMessage::ImageConvert(
823 SendablePtr {
824 ptr: NonNull::from(src),
825 len: 1,
826 },
827 SendablePtr {
828 ptr: NonNull::from(dst),
829 len: 1,
830 },
831 rotation,
832 flip,
833 crop,
834 true, err_send,
836 ))
837 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
838 err_recv.blocking_recv().map_err(|_| {
839 Error::Internal("GL converter error messaging closed without update".to_string())
840 })?
841 }
842
843 fn flush(&mut self) -> crate::Result<()> {
844 let (err_send, err_recv) = tokio::sync::oneshot::channel();
845 self.sender
846 .as_ref()
847 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
848 .blocking_send(GLProcessorMessage::Flush(err_send))
849 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
850 err_recv.blocking_recv().map_err(|_| {
851 Error::Internal("GL converter error messaging closed without update".to_string())
852 })?
853 }
854
855 fn draw_decoded_masks(
856 &mut self,
857 dst: &mut TensorDyn,
858 detect: &[crate::DetectBox],
859 segmentation: &[crate::Segmentation],
860 overlay: crate::MaskOverlay<'_>,
861 ) -> crate::Result<()> {
862 let (err_send, err_recv) = tokio::sync::oneshot::channel();
863 self.sender
864 .as_ref()
865 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
866 .blocking_send(GLProcessorMessage::DrawDecodedMasks(
867 SendablePtr {
868 ptr: NonNull::from(dst),
869 len: 1,
870 },
871 SendablePtr {
872 ptr: NonNull::new(detect.as_ptr() as *mut DetectBox).unwrap(),
873 len: detect.len(),
874 },
875 SendablePtr {
876 ptr: NonNull::new(segmentation.as_ptr() as *mut Segmentation).unwrap(),
877 len: segmentation.len(),
878 },
879 overlay.opacity,
880 overlay.background.map(|bg| SendablePtr {
881 ptr: NonNull::from(bg).cast::<TensorDyn>(),
882 len: 1,
883 }),
884 overlay.letterbox,
885 overlay.color_mode,
886 err_send,
887 ))
888 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
889 err_recv.blocking_recv().map_err(|_| {
890 Error::Internal("GL converter error messaging closed without update".to_string())
891 })?
892 }
893
894 fn draw_proto_masks(
895 &mut self,
896 dst: &mut TensorDyn,
897 detect: &[DetectBox],
898 proto_data: &ProtoData,
899 overlay: crate::MaskOverlay<'_>,
900 ) -> crate::Result<()> {
901 let (err_send, err_recv) = tokio::sync::oneshot::channel();
902 self.sender
903 .as_ref()
904 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
905 .blocking_send(GLProcessorMessage::DrawProtoMasks(
906 SendablePtr {
907 ptr: NonNull::from(dst),
908 len: 1,
909 },
910 SendablePtr {
911 ptr: NonNull::new(detect.as_ptr() as *mut DetectBox).unwrap(),
912 len: detect.len(),
913 },
914 SendablePtr {
915 ptr: NonNull::from(proto_data).cast::<ProtoData>(),
916 len: 1,
917 },
918 overlay.opacity,
919 overlay.background.map(|bg| SendablePtr {
920 ptr: NonNull::from(bg).cast::<TensorDyn>(),
921 len: 1,
922 }),
923 overlay.letterbox,
924 overlay.color_mode,
925 err_send,
926 ))
927 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
928 err_recv.blocking_recv().map_err(|_| {
929 Error::Internal("GL converter error messaging closed without update".to_string())
930 })?
931 }
932
933 fn set_class_colors(&mut self, colors: &[[u8; 4]]) -> Result<(), crate::Error> {
934 let (err_send, err_recv) = tokio::sync::oneshot::channel();
935 self.sender
936 .as_ref()
937 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
938 .blocking_send(GLProcessorMessage::SetColors(colors.to_vec(), err_send))
939 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
940 err_recv.blocking_recv().map_err(|_| {
941 Error::Internal("GL converter error messaging closed without update".to_string())
942 })?
943 }
944}
945
946impl GLProcessorThreaded {
947 pub fn set_colorimetry_mode(&mut self, mode: crate::ColorimetryMode) -> Result<(), Error> {
952 let (err_send, err_recv) = tokio::sync::oneshot::channel();
953 self.sender
954 .as_ref()
955 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
956 .blocking_send(GLProcessorMessage::SetColorimetryMode(mode, err_send))
957 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
958 err_recv.blocking_recv().map_err(|_| {
959 Error::Internal("GL converter error messaging closed without update".to_string())
960 })?
961 }
962
963 pub fn set_int8_interpolation_mode(
965 &mut self,
966 mode: Int8InterpolationMode,
967 ) -> Result<(), crate::Error> {
968 let (err_send, err_recv) = tokio::sync::oneshot::channel();
969 self.sender
970 .as_ref()
971 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
972 .blocking_send(GLProcessorMessage::SetInt8Interpolation(mode, err_send))
973 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
974 err_recv.blocking_recv().map_err(|_| {
975 Error::Internal("GL converter error messaging closed without update".to_string())
976 })?
977 }
978
979 pub fn egl_cache_stats(&self) -> Result<super::cache::GlCacheStats, Error> {
986 let (send, recv) = tokio::sync::oneshot::channel();
987 self.sender
988 .as_ref()
989 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
990 .blocking_send(GLProcessorMessage::EglCacheStats(send))
991 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
992 recv.blocking_recv().map_err(|_| {
993 Error::Internal("GL converter error messaging closed without update".to_string())
994 })?
995 }
996
997 pub fn create_pbo_image(
999 &self,
1000 width: usize,
1001 height: usize,
1002 format: edgefirst_tensor::PixelFormat,
1003 ) -> Result<edgefirst_tensor::Tensor<u8>, Error> {
1004 let sender = self
1005 .sender
1006 .as_ref()
1007 .ok_or(Error::OpenGl("GL processor is shutting down".to_string()))?;
1008
1009 let size = pbo_elem_count(width, height, format)
1010 .filter(|&s| s != 0)
1011 .ok_or_else(|| Error::OpenGl("Invalid image dimensions".to_string()))?;
1012
1013 let (tx, rx) = tokio::sync::oneshot::channel();
1015 sender
1016 .blocking_send(GLProcessorMessage::PboCreate(size, tx))
1017 .map_err(|_| Error::OpenGl("GL thread channel closed".to_string()))?;
1018 let buffer_id = rx
1019 .blocking_recv()
1020 .map_err(|_| Error::OpenGl("GL thread did not respond".to_string()))??;
1021
1022 let ops: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1023 sender: sender.downgrade(),
1024 });
1025
1026 let shape = pbo_shape(width, height, format);
1027
1028 let pbo_tensor =
1029 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops)
1030 .map_err(|e| Error::OpenGl(format!("PBO tensor creation failed: {e:?}")))?;
1031 let mut tensor = edgefirst_tensor::Tensor::from_pbo(pbo_tensor);
1032 tensor
1033 .set_format(format)
1034 .map_err(|e| Error::OpenGl(format!("Failed to set format on PBO tensor: {e:?}")))?;
1035 register_pbo_cuda(&mut tensor, buffer_id, size, sender);
1042 Ok(tensor)
1043 }
1044
1045 pub(crate) fn create_pbo_image_dtype(
1051 &self,
1052 width: usize,
1053 height: usize,
1054 format: edgefirst_tensor::PixelFormat,
1055 dtype: edgefirst_tensor::DType,
1056 ) -> Result<TensorDyn, Error> {
1057 let sender = self
1058 .sender
1059 .as_ref()
1060 .ok_or(Error::OpenGl("GL processor is shutting down".to_string()))?;
1061
1062 let elems = pbo_elem_count(width, height, format)
1063 .filter(|&e| e != 0)
1064 .ok_or_else(|| Error::OpenGl("Invalid image dimensions".to_string()))?;
1065
1066 let size = elems
1067 .checked_mul(dtype.size())
1068 .ok_or_else(|| Error::OpenGl("PBO size overflow".to_string()))?;
1069
1070 let (tx, rx) = tokio::sync::oneshot::channel();
1072 sender
1073 .blocking_send(GLProcessorMessage::PboCreate(size, tx))
1074 .map_err(|_| Error::OpenGl("GL thread channel closed".to_string()))?;
1075 let buffer_id = rx
1076 .blocking_recv()
1077 .map_err(|_| Error::OpenGl("GL thread did not respond".to_string()))??;
1078
1079 let ops: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1080 sender: sender.downgrade(),
1081 });
1082
1083 let shape = pbo_shape(width, height, format);
1084
1085 let map_err = |e: edgefirst_tensor::Error| {
1086 Error::OpenGl(format!("PBO tensor creation failed: {e:?}"))
1087 };
1088 let set_err = |e: edgefirst_tensor::Error| {
1089 Error::OpenGl(format!("Failed to set format on PBO tensor: {e:?}"))
1090 };
1091
1092 match dtype {
1093 edgefirst_tensor::DType::U8 => {
1094 let pbo =
1095 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops)
1096 .map_err(map_err)?;
1097 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo);
1098 t.set_format(format).map_err(set_err)?;
1099 register_pbo_cuda(&mut t, buffer_id, size, sender);
1100 Ok(TensorDyn::from(t))
1101 }
1102 edgefirst_tensor::DType::F16 => {
1103 let pbo = edgefirst_tensor::PboTensor::<edgefirst_tensor::f16>::from_pbo(
1104 buffer_id, size, &shape, None, ops,
1105 )
1106 .map_err(map_err)?;
1107 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo);
1108 t.set_format(format).map_err(set_err)?;
1109 register_pbo_cuda(&mut t, buffer_id, size, sender);
1110 Ok(TensorDyn::from(t))
1111 }
1112 edgefirst_tensor::DType::F32 => {
1113 let pbo = edgefirst_tensor::PboTensor::<f32>::from_pbo(
1114 buffer_id, size, &shape, None, ops,
1115 )
1116 .map_err(map_err)?;
1117 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo);
1118 t.set_format(format).map_err(set_err)?;
1119 register_pbo_cuda(&mut t, buffer_id, size, sender);
1120 Ok(TensorDyn::from(t))
1121 }
1122 other => Err(Error::OpenGl(format!("unsupported PBO dtype {other:?}"))),
1123 }
1124 }
1125
1126 pub(crate) fn transfer_backend(&self) -> TransferBackend {
1128 self.caps.transfer_backend
1129 }
1130
1131 pub(crate) fn supported_render_dtypes(&self) -> crate::RenderDtypeSupport {
1138 self.caps.render_dtypes
1139 }
1140}
1141
1142impl Drop for GLProcessorThreaded {
1143 fn drop(&mut self) {
1144 drop(self.sender.take());
1145 let _ = self.handle.take().and_then(|h| h.join().ok());
1146 }
1147}
1148
1149#[cfg(test)]
1153#[cfg_attr(coverage_nightly, coverage(off))]
1154mod tests {
1155 use super::{pbo_elem_count, pbo_shape};
1156 use edgefirst_tensor::PixelFormat;
1157
1158 #[test]
1159 fn elem_count_per_format() {
1160 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Rgba), Some(8 * 4 * 4));
1162 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Rgb), Some(8 * 4 * 3));
1164 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Nv12), Some(8 * 4 * 3 / 2));
1166 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Nv16), Some(8 * 4 * 2));
1168 }
1169
1170 #[test]
1171 fn elem_count_overflow_is_none() {
1172 assert_eq!(pbo_elem_count(usize::MAX, 2, PixelFormat::Rgba), None);
1174 assert_eq!(pbo_elem_count(usize::MAX, 1, PixelFormat::Rgb), None);
1176 }
1177
1178 #[test]
1179 fn shape_per_format() {
1180 assert_eq!(pbo_shape(8, 4, PixelFormat::PlanarRgb), vec![3, 4, 8]);
1182 assert_eq!(pbo_shape(8, 4, PixelFormat::Nv12), vec![4 * 3 / 2, 8]);
1184 assert_eq!(pbo_shape(8, 4, PixelFormat::Nv16), vec![4 * 2, 8]);
1186 assert_eq!(pbo_shape(8, 4, PixelFormat::Rgba), vec![4, 8, 4]);
1188 }
1189}