1use edgefirst_tensor::{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::{ConvertOptions, Error, ImageProcessorTrait};
14use edgefirst_tensor::TensorDyn;
15
16#[allow(clippy::type_complexity)]
17enum GLProcessorMessage {
18 ImageConvert(
19 SendablePtr<TensorDyn>,
20 SendablePtr<TensorDyn>,
21 ConvertOptions,
22 bool, tokio::sync::oneshot::Sender<Result<(), Error>>,
24 ),
25 ImageConvertFenced(
29 SendablePtr<TensorDyn>,
30 SendablePtr<TensorDyn>,
31 ConvertOptions,
32 tokio::sync::oneshot::Sender<Result<Option<super::CompletionFence>, Error>>,
33 ),
34 Flush(tokio::sync::oneshot::Sender<Result<(), Error>>),
36 SetColors(
37 Vec<[u8; 4]>,
38 tokio::sync::oneshot::Sender<Result<(), Error>>,
39 ),
40 DrawDecodedMasks(
41 SendablePtr<TensorDyn>,
42 SendablePtr<DetectBox>,
43 SendablePtr<Segmentation>,
44 f32, Option<SendablePtr<TensorDyn>>, Option<[f32; 4]>, crate::ColorMode,
48 tokio::sync::oneshot::Sender<Result<(), Error>>,
49 ),
50 DrawProtoMasks(
51 SendablePtr<TensorDyn>,
52 SendablePtr<DetectBox>,
53 SendablePtr<ProtoData>,
54 f32, Option<SendablePtr<TensorDyn>>, Option<[f32; 4]>, crate::ColorMode,
58 tokio::sync::oneshot::Sender<Result<(), Error>>,
59 ),
60 SetInt8Interpolation(
61 Int8InterpolationMode,
62 tokio::sync::oneshot::Sender<Result<(), Error>>,
63 ),
64 SetColorimetryMode(
66 crate::ColorimetryMode,
67 tokio::sync::oneshot::Sender<Result<(), Error>>,
68 ),
69 SetInterpolation(
71 crate::Interpolation,
72 tokio::sync::oneshot::Sender<Result<(), Error>>,
73 ),
74 #[cfg(test)]
76 SetFailDstImport(bool, tokio::sync::oneshot::Sender<Result<(), Error>>),
77 EglCacheStats(tokio::sync::oneshot::Sender<Result<super::cache::GlCacheStats, Error>>),
79 ConvertStats(tokio::sync::oneshot::Sender<Result<super::cache::ConvertStats, Error>>),
81 PboCreate(
82 usize, tokio::sync::oneshot::Sender<Result<u32, Error>>,
84 ),
85 PboMap(
86 u32, usize, tokio::sync::oneshot::Sender<Result<edgefirst_tensor::PboMapping, Error>>,
89 ),
90 PboUnmap(
91 u32, tokio::sync::oneshot::Sender<Result<(), Error>>,
93 ),
94 PboDelete(u32), CudaRegisterBuffer(u32, tokio::sync::oneshot::Sender<Option<usize>>),
96 CudaMap(usize, tokio::sync::oneshot::Sender<Option<(usize, usize)>>),
97 CudaUnmap(usize), CudaUnregister(usize), }
100
101fn pbo_elem_count(
111 width: usize,
112 height: usize,
113 format: edgefirst_tensor::PixelFormat,
114) -> Option<usize> {
115 format
122 .allocation_shape(width, height)?
123 .iter()
124 .try_fold(1usize, |acc, &d| acc.checked_mul(d))
125}
126
127fn pbo_shape(width: usize, height: usize, format: edgefirst_tensor::PixelFormat) -> Vec<usize> {
131 format
132 .allocation_shape(width, height)
133 .unwrap_or_else(|| vec![height, width, format.channels()])
134}
135
136fn register_pbo_cuda<T>(
145 tensor: &mut edgefirst_tensor::Tensor<T>,
146 buffer_id: u32,
147 size: usize,
148 sender: &Sender<GLProcessorMessage>,
149) where
150 T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync + edgefirst_tensor::Element,
151{
152 if !edgefirst_tensor::is_cuda_available() {
153 return;
154 }
155 let (tx, rx) = tokio::sync::oneshot::channel();
156 if sender
157 .blocking_send(GLProcessorMessage::CudaRegisterBuffer(buffer_id, tx))
158 .is_ok()
159 {
160 if let Ok(Some(resource)) = rx.blocking_recv() {
161 let ops = std::sync::Arc::new(GlCudaOps {
162 sender: sender.downgrade(),
163 });
164 tensor.set_cuda_handle(edgefirst_tensor::CudaHandle::new_gl(
165 resource as *mut std::ffi::c_void,
166 size,
167 ops,
168 ));
169 }
170 }
171}
172
173struct GlPboOps {
180 sender: WeakSender<GLProcessorMessage>,
181 worker_id: u64,
186}
187
188static NEXT_GL_WORKER_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
190
191struct GlWorkerState {
193 id: u64,
194 serializes: bool,
197 mapped: std::collections::HashSet<u32>,
199}
200
201thread_local! {
202 static GL_WORKER: std::cell::RefCell<Option<GlWorkerState>> =
218 const { std::cell::RefCell::new(None) };
219}
220
221fn with_worker_mapped<R>(f: impl FnOnce(&mut std::collections::HashSet<u32>) -> R) -> Option<R> {
225 GL_WORKER.with(|c| c.borrow_mut().as_mut().map(|s| f(&mut s.mapped)))
226}
227
228fn on_owning_gl_worker(worker_id: u64) -> bool {
230 GL_WORKER.with(|c| matches!(*c.borrow(), Some(ref s) if s.id == worker_id))
231}
232
233fn on_serializing_gl_worker() -> bool {
236 GL_WORKER.with(|c| matches!(*c.borrow(), Some(ref s) if s.serializes))
237}
238
239fn cross_context_pbo_error(op: &str, buffer_id: u32) -> edgefirst_tensor::Error {
243 edgefirst_tensor::Error::InvalidOperation(format!(
244 "PBO {buffer_id}: cannot {op} a buffer owned by another GL context while \
245 this one holds the serialization lock; convert it through the processor \
246 that created it"
247 ))
248}
249
250thread_local! {
251 static GL_WORKER_POISON: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
254}
255
256fn inline_gl_op<T>(what: &str, f: impl FnOnce() -> Result<T, Error>) -> Result<T, Error> {
264 match std::panic::catch_unwind(AssertUnwindSafe(f)) {
265 Ok(result) => result,
266 Err(e) => {
267 GL_WORKER_POISON.with(|p| p.set(true));
268 Err(Error::Internal(format!(
269 "GL thread panicked during {what}: {}",
270 panic_message(e.as_ref()),
271 )))
272 }
273 }
274}
275
276unsafe impl edgefirst_tensor::PboOps for GlPboOps {
282 fn map_buffer(
283 &self,
284 buffer_id: u32,
285 size: usize,
286 ) -> edgefirst_tensor::Result<edgefirst_tensor::PboMapping> {
287 if on_owning_gl_worker(self.worker_id) {
292 return inline_gl_op("PboMap", || pbo_map_on_gl_thread(buffer_id, size)).map_err(|e| {
293 edgefirst_tensor::Error::NotImplemented(format!("GL PBO map failed: {e:?}"))
294 });
295 }
296 if on_serializing_gl_worker() {
298 return Err(cross_context_pbo_error("map", buffer_id));
299 }
300 let sender = self
301 .sender
302 .upgrade()
303 .ok_or(edgefirst_tensor::Error::PboDisconnected)?;
304 let (tx, rx) = tokio::sync::oneshot::channel();
305 sender
306 .blocking_send(GLProcessorMessage::PboMap(buffer_id, size, tx))
307 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?;
308 rx.blocking_recv()
309 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?
310 .map_err(|e| {
311 edgefirst_tensor::Error::NotImplemented(format!("GL PBO map failed: {e:?}"))
312 })
313 }
314
315 fn unmap_buffer(&self, buffer_id: u32) -> edgefirst_tensor::Result<()> {
316 if on_owning_gl_worker(self.worker_id) {
318 return inline_gl_op("PboUnmap", || pbo_unmap_on_gl_thread(buffer_id)).map_err(|e| {
319 edgefirst_tensor::Error::NotImplemented(format!("GL PBO unmap failed: {e:?}"))
320 });
321 }
322 if on_serializing_gl_worker() {
324 return Err(cross_context_pbo_error("unmap", buffer_id));
325 }
326 let sender = self
327 .sender
328 .upgrade()
329 .ok_or(edgefirst_tensor::Error::PboDisconnected)?;
330 let (tx, rx) = tokio::sync::oneshot::channel();
331 sender
332 .blocking_send(GLProcessorMessage::PboUnmap(buffer_id, tx))
333 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?;
334 rx.blocking_recv()
335 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?
336 .map_err(|e| {
337 edgefirst_tensor::Error::NotImplemented(format!("GL PBO unmap failed: {e:?}"))
338 })
339 }
340
341 fn delete_buffer(&self, buffer_id: u32) {
342 if on_owning_gl_worker(self.worker_id) {
344 with_worker_mapped(|set| set.remove(&buffer_id));
345 let _ = inline_gl_op("PboDelete", || {
348 unsafe { edgefirst_gl::gl::DeleteBuffers(1, &buffer_id) };
349 Ok(())
350 });
351 return;
352 }
353 if let Some(sender) = self.sender.upgrade() {
354 if on_serializing_gl_worker() {
356 if sender
357 .try_send(GLProcessorMessage::PboDelete(buffer_id))
358 .is_err()
359 {
360 log::warn!(
361 "PBO {buffer_id}: cross-context delete skipped to avoid a deadlock; \
362 the buffer is freed when its context is destroyed"
363 );
364 }
365 return;
366 }
367 let _ = sender.blocking_send(GLProcessorMessage::PboDelete(buffer_id));
368 }
369 }
370}
371
372struct GlCudaOps {
379 sender: WeakSender<GLProcessorMessage>,
380}
381
382impl edgefirst_tensor::CudaGlOps for GlCudaOps {
383 fn map(&self, resource: *mut std::ffi::c_void) -> Option<(*mut std::ffi::c_void, usize)> {
384 let sender = self.sender.upgrade()?;
385 let (tx, rx) = tokio::sync::oneshot::channel();
386 sender
387 .blocking_send(GLProcessorMessage::CudaMap(resource as usize, tx))
388 .ok()?;
389 rx.blocking_recv()
390 .ok()?
391 .map(|(p, l)| (p as *mut std::ffi::c_void, l))
392 }
393
394 fn unmap(&self, resource: *mut std::ffi::c_void) {
395 if let Some(sender) = self.sender.upgrade() {
396 let _ = sender.blocking_send(GLProcessorMessage::CudaUnmap(resource as usize));
397 }
398 }
399
400 fn unregister(&self, resource: *mut std::ffi::c_void) {
401 if let Some(sender) = self.sender.upgrade() {
402 let _ = sender.blocking_send(GLProcessorMessage::CudaUnregister(resource as usize));
403 }
404 }
405}
406
407#[derive(Debug)]
412pub struct GLProcessorThreaded {
413 handle: Option<JoinHandle<()>>,
415
416 sender: Option<Sender<GLProcessorMessage>>,
418 worker_id: u64,
421 owned_pbos: std::sync::Arc<super::processor::OwnedPbos>,
424 caps: super::platform::PlatformCaps,
428}
429
430unsafe impl Send for GLProcessorThreaded {}
431unsafe impl Sync for GLProcessorThreaded {}
432
433struct SendablePtr<T: Send> {
434 ptr: NonNull<T>,
435 len: usize,
436}
437
438unsafe impl<T> Send for SendablePtr<T> where T: Send {}
439
440fn panic_message(info: &(dyn std::any::Any + Send)) -> String {
442 if let Some(s) = info.downcast_ref::<&str>() {
443 s.to_string()
444 } else if let Some(s) = info.downcast_ref::<String>() {
445 s.clone()
446 } else {
447 "unknown panic".to_string()
448 }
449}
450
451fn reply_caught<T>(
452 result: std::thread::Result<crate::Result<T>>,
453 resp: tokio::sync::oneshot::Sender<crate::Result<T>>,
454 poisoned: &mut bool,
455 what: &str,
456) {
457 let _ = resp.send(match result {
458 Ok(res) => res,
459 Err(e) => {
460 *poisoned = true;
461 Err(crate::Error::Internal(format!(
462 "GL thread panicked during {what}: {}",
463 panic_message(e.as_ref()),
464 )))
465 }
466 });
467}
468
469fn reject_poisoned_message(msg: GLProcessorMessage) {
470 let poison_err =
471 crate::Error::Internal("GL context is poisoned after a prior panic".to_string());
472 match msg {
473 GLProcessorMessage::ImageConvertFenced(.., resp) => {
474 let _ = resp.send(Err(poison_err));
475 }
476 GLProcessorMessage::ImageConvert(.., resp) => {
477 let _ = resp.send(Err(poison_err));
478 }
479 GLProcessorMessage::Flush(resp) => {
480 let _ = resp.send(Err(poison_err));
481 }
482 GLProcessorMessage::DrawDecodedMasks(.., resp) => {
483 let _ = resp.send(Err(poison_err));
484 }
485 GLProcessorMessage::DrawProtoMasks(.., resp) => {
486 let _ = resp.send(Err(poison_err));
487 }
488 GLProcessorMessage::SetColors(_, resp) => {
489 let _ = resp.send(Err(poison_err));
490 }
491 GLProcessorMessage::SetInt8Interpolation(_, resp) => {
492 let _ = resp.send(Err(poison_err));
493 }
494 GLProcessorMessage::SetColorimetryMode(_, resp) => {
495 let _ = resp.send(Err(poison_err));
496 }
497 GLProcessorMessage::SetInterpolation(_, resp) => {
498 let _ = resp.send(Err(poison_err));
499 }
500 #[cfg(test)]
501 GLProcessorMessage::SetFailDstImport(_, resp) => {
502 let _ = resp.send(Err(poison_err));
503 }
504 GLProcessorMessage::EglCacheStats(resp) => {
505 let _ = resp.send(Err(poison_err));
506 }
507 GLProcessorMessage::ConvertStats(resp) => {
508 let _ = resp.send(Err(poison_err));
509 }
510 GLProcessorMessage::PboCreate(_, resp) => {
511 let _ = resp.send(Err(poison_err));
512 }
513 GLProcessorMessage::PboMap(_, _, resp) => {
514 let _ = resp.send(Err(poison_err));
515 }
516 GLProcessorMessage::PboUnmap(_, resp) => {
517 let _ = resp.send(Err(poison_err));
518 }
519 GLProcessorMessage::PboDelete(_) => {}
520 GLProcessorMessage::CudaRegisterBuffer(_, resp) => {
521 let _ = resp.send(None);
522 }
523 GLProcessorMessage::CudaMap(_, resp) => {
524 let _ = resp.send(None);
525 }
526 GLProcessorMessage::CudaUnmap(_) => {}
530 GLProcessorMessage::CudaUnregister(_) => {}
531 }
532}
533
534fn run_gl_worker(
535 worker_id: u64,
536 owned_pbos: std::sync::Arc<super::processor::OwnedPbos>,
537 kind: Option<EglDisplayKind>,
538 capacity: Option<usize>,
539 mut recv: tokio::sync::mpsc::Receiver<GLProcessorMessage>,
540 create_ctx_send: tokio::sync::oneshot::Sender<Result<super::platform::PlatformCaps, Error>>,
541) {
542 #[cfg(target_os = "linux")]
547 let init_result = {
548 let _guard = super::context::GL_MUTEX
549 .lock()
550 .unwrap_or_else(|e| e.into_inner());
551 GLProcessorST::new(kind, capacity)
552 };
553 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
557 let init_result = GLProcessorST::new(kind, capacity);
558 #[cfg(target_os = "windows")]
566 let init_result = {
567 let _guard = NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
568 GLProcessorST::new(kind, capacity)
569 };
570 let mut gl_converter = match init_result {
571 Ok(gl) => gl,
572 Err(e) => {
573 let _ = create_ctx_send.send(Err(e));
574 return;
575 }
576 };
577 gl_converter.owned_pbos = owned_pbos;
578 let caps = gl_converter.platform_caps();
581 let _ = create_ctx_send.send(Ok(caps));
582 let mut poisoned = false;
583 GL_WORKER.with(|c| {
605 *c.borrow_mut() = Some(GlWorkerState {
606 id: worker_id,
607 serializes: false,
609 mapped: std::collections::HashSet::new(),
610 });
611 });
612 struct GlWorkerSlot;
615 impl Drop for GlWorkerSlot {
616 fn drop(&mut self) {
617 GL_WORKER.with(|c| *c.borrow_mut() = None);
618 GL_WORKER_POISON.with(|p| p.set(false));
619 }
620 }
621 let _gl_worker_slot = GlWorkerSlot;
622 let serialize_per_msg = match std::env::var("EDGEFIRST_GL_SERIALIZE").as_deref() {
631 Ok("full") => true,
632 Ok("lifecycle") => {
633 #[cfg(target_os = "windows")]
638 log::warn!(
639 "EDGEFIRST_GL_SERIALIZE=lifecycle drops the per-message lock the ANGLE/D3D11 context re-sync relies on; concurrent GL across processors is not supported on this backend"
640 );
641 false
642 }
643 _ => caps.serialize_gl,
644 };
645 log::debug!(
646 "GL serialization policy: {}",
647 if serialize_per_msg {
648 "Full (per-message global lock)"
649 } else {
650 "LifecycleOnly (parallel across processors)"
651 }
652 );
653 GL_WORKER.with(|c| {
654 if let Some(state) = c.borrow_mut().as_mut() {
655 state.serializes = serialize_per_msg;
656 }
657 });
658 while let Some(msg) = recv.blocking_recv() {
659 #[cfg(target_os = "linux")]
668 let _guard = serialize_per_msg.then(|| {
669 super::context::GL_MUTEX
670 .lock()
671 .unwrap_or_else(|e| e.into_inner())
672 });
673 #[cfg(any(
674 target_os = "macos",
675 target_os = "ios",
676 target_os = "android",
677 target_os = "windows"
678 ))]
679 let _guard =
680 serialize_per_msg.then(|| NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner()));
681
682 if poisoned {
687 reject_poisoned_message(msg);
688 continue;
689 }
690
691 gl_converter.begin_gpu_pass();
695 handle_gl_message(msg, &mut gl_converter, &mut poisoned);
696 poisoned |= GL_WORKER_POISON.with(|p| p.replace(false));
700 gl_converter.end_gpu_pass_if_synced();
704 }
705 #[cfg(target_os = "linux")]
709 let _guard = super::context::GL_MUTEX
710 .lock()
711 .unwrap_or_else(|e| e.into_inner());
712 #[cfg(target_os = "windows")]
713 let _guard = NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
714 drop(gl_converter);
715}
716
717#[cfg(not(target_os = "linux"))]
721static NON_LINUX_GL_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
722
723#[cfg(target_os = "windows")]
734pub(super) fn lifecycle_guard() -> std::sync::MutexGuard<'static, ()> {
735 NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner())
736}
737
738fn handle_gl_message(
739 msg: GLProcessorMessage,
740 gl_converter: &mut GLProcessorST,
741 poisoned: &mut bool,
742) {
743 match msg {
744 GLProcessorMessage::ImageConvert(src, dst, options, defer, resp) => {
745 handle_image_convert(src, dst, (options, defer), resp, gl_converter, poisoned);
746 }
747 GLProcessorMessage::ImageConvertFenced(src, dst, options, resp) => {
748 handle_image_convert_fenced(src, dst, options, resp, gl_converter, poisoned);
749 }
750 GLProcessorMessage::Flush(resp) => {
751 let result = std::panic::catch_unwind(AssertUnwindSafe(|| gl_converter.flush()));
752 reply_caught(result, resp, poisoned, "Flush");
753 }
754 GLProcessorMessage::DrawDecodedMasks(
755 dst,
756 det,
757 seg,
758 opacity,
759 bg,
760 letterbox,
761 color_mode,
762 resp,
763 ) => {
764 handle_draw_decoded_masks(
765 dst,
766 det,
767 seg,
768 (opacity, bg, letterbox, color_mode),
769 resp,
770 gl_converter,
771 poisoned,
772 );
773 }
774 GLProcessorMessage::DrawProtoMasks(
775 dst,
776 det,
777 proto_data,
778 opacity,
779 bg,
780 letterbox,
781 color_mode,
782 resp,
783 ) => {
784 handle_draw_proto_masks(
785 dst,
786 det,
787 proto_data,
788 (opacity, bg, letterbox, color_mode),
789 resp,
790 gl_converter,
791 poisoned,
792 );
793 }
794 GLProcessorMessage::SetColors(colors, resp) => {
795 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
796 gl_converter.set_class_colors(&colors)
797 }));
798 reply_caught(result, resp, poisoned, "SetColors");
799 }
800 GLProcessorMessage::SetColorimetryMode(mode, resp) => {
801 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
802 gl_converter.set_colorimetry_mode(mode);
803 Ok(())
804 }));
805 reply_caught(result, resp, poisoned, "SetColorimetryMode");
806 }
807 GLProcessorMessage::SetInterpolation(mode, resp) => {
808 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
809 gl_converter.set_interpolation(mode);
810 Ok(())
811 }));
812 reply_caught(result, resp, poisoned, "SetInterpolation");
813 }
814 GLProcessorMessage::SetInt8Interpolation(mode, resp) => {
815 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
816 gl_converter.set_int8_interpolation_mode(mode);
817 Ok(())
818 }));
819 reply_caught(result, resp, poisoned, "SetInt8Interpolation");
820 }
821 #[cfg(test)]
822 GLProcessorMessage::SetFailDstImport(fail, resp) => {
823 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
824 gl_converter.fail_dst_import = fail;
825 Ok(())
826 }));
827 reply_caught(result, resp, poisoned, "SetFailDstImport");
828 }
829 GLProcessorMessage::EglCacheStats(resp) => {
830 let result =
831 std::panic::catch_unwind(AssertUnwindSafe(|| Ok(gl_converter.egl_cache_stats())));
832 reply_caught(result, resp, poisoned, "EglCacheStats");
833 }
834 GLProcessorMessage::ConvertStats(resp) => {
835 let result =
836 std::panic::catch_unwind(AssertUnwindSafe(|| Ok(gl_converter.convert_stats())));
837 reply_caught(result, resp, poisoned, "ConvertStats");
838 }
839 GLProcessorMessage::PboCreate(size, resp) => {
840 handle_pbo_create(size, resp, poisoned);
841 }
842 GLProcessorMessage::PboMap(buffer_id, size, resp) => {
843 handle_pbo_map(buffer_id, size, resp, poisoned);
844 }
845 GLProcessorMessage::PboUnmap(buffer_id, resp) => {
846 handle_pbo_unmap(buffer_id, resp, poisoned);
847 }
848 GLProcessorMessage::PboDelete(buffer_id) => {
849 handle_pbo_delete(buffer_id, poisoned);
850 }
851 GLProcessorMessage::CudaRegisterBuffer(buffer_id, resp) => {
852 let _ = resp.send(edgefirst_tensor::gl_register_buffer(buffer_id));
854 }
855 GLProcessorMessage::CudaMap(resource, resp) => {
856 gl_converter.flush_pending();
863 let _ = resp.send(edgefirst_tensor::gl_map_resource(resource));
864 }
865 GLProcessorMessage::CudaUnmap(resource) => {
866 edgefirst_tensor::gl_unmap_resource(resource);
867 }
868 GLProcessorMessage::CudaUnregister(resource) => {
869 edgefirst_tensor::gl_unregister_resource(resource);
870 }
871 }
872}
873
874fn handle_image_convert(
875 src: SendablePtr<TensorDyn>,
876 mut dst: SendablePtr<TensorDyn>,
877 geom: (ConvertOptions, bool),
878 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
879 gl_converter: &mut GLProcessorST,
880 poisoned: &mut bool,
881) {
882 let (options, defer) = geom;
883 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
886 let src = unsafe { src.ptr.as_ref() };
887 let dst = unsafe { dst.ptr.as_mut() };
888 if defer {
892 gl_converter.convert_deferred(src, dst, Some(options))
893 } else {
894 gl_converter.convert(src, dst, Some(options))
895 }
896 }));
897 reply_caught(result, resp, poisoned, "ImageConvert");
898}
899
900fn handle_image_convert_fenced(
901 src: SendablePtr<TensorDyn>,
902 mut dst: SendablePtr<TensorDyn>,
903 options: ConvertOptions,
904 resp: tokio::sync::oneshot::Sender<Result<Option<super::CompletionFence>, Error>>,
905 gl_converter: &mut GLProcessorST,
906 poisoned: &mut bool,
907) {
908 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
911 let src = unsafe { src.ptr.as_ref() };
912 let dst = unsafe { dst.ptr.as_mut() };
913 gl_converter.convert_fenced(src, dst, options)
914 }));
915 reply_caught(result, resp, poisoned, "ImageConvertFenced");
916}
917
918fn handle_draw_decoded_masks(
919 mut dst: SendablePtr<TensorDyn>,
920 det: SendablePtr<DetectBox>,
921 seg: SendablePtr<Segmentation>,
922 overlay: (
923 f32,
924 Option<SendablePtr<TensorDyn>>,
925 Option<[f32; 4]>,
926 crate::ColorMode,
927 ),
928 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
929 gl_converter: &mut GLProcessorST,
930 poisoned: &mut bool,
931) {
932 let (opacity, bg, letterbox, color_mode) = overlay;
933 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
937 let dst = unsafe { dst.ptr.as_mut() };
938 let det = unsafe { std::slice::from_raw_parts(det.ptr.as_ptr(), det.len) };
939 let seg = unsafe { std::slice::from_raw_parts(seg.ptr.as_ptr(), seg.len) };
940 let bg_ref = bg.map(|p| unsafe { &*p.ptr.as_ptr() });
941 gl_converter.draw_decoded_masks(
942 dst,
943 det,
944 seg,
945 crate::MaskOverlay {
946 background: bg_ref,
947 opacity,
948 letterbox,
949 color_mode,
950 },
951 )
952 }));
953 reply_caught(result, resp, poisoned, "DrawDecodedMasks");
954}
955
956fn handle_draw_proto_masks(
957 mut dst: SendablePtr<TensorDyn>,
958 det: SendablePtr<DetectBox>,
959 proto_data: SendablePtr<ProtoData>,
960 overlay: (
961 f32,
962 Option<SendablePtr<TensorDyn>>,
963 Option<[f32; 4]>,
964 crate::ColorMode,
965 ),
966 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
967 gl_converter: &mut GLProcessorST,
968 poisoned: &mut bool,
969) {
970 let (opacity, bg, letterbox, color_mode) = overlay;
971 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
974 let dst = unsafe { dst.ptr.as_mut() };
975 let det = unsafe { std::slice::from_raw_parts(det.ptr.as_ptr(), det.len) };
976 let bg_ref = bg.map(|p| unsafe { &*p.ptr.as_ptr() });
977 let proto_data = unsafe { proto_data.ptr.as_ref() };
978 gl_converter.draw_proto_masks(
979 dst,
980 det,
981 proto_data,
982 crate::MaskOverlay {
983 background: bg_ref,
984 opacity,
985 letterbox,
986 color_mode,
987 },
988 )
989 }));
990 reply_caught(result, resp, poisoned, "DrawProtoMasks");
991}
992
993fn handle_pbo_create(
994 size: usize,
995 resp: tokio::sync::oneshot::Sender<Result<u32, Error>>,
996 poisoned: &mut bool,
997) {
998 let result = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
999 let mut id: u32 = 0;
1000 edgefirst_gl::gl::GenBuffers(1, &mut id);
1001 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, id);
1002 edgefirst_gl::gl::BufferData(
1003 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
1004 size as isize,
1005 std::ptr::null(),
1006 edgefirst_gl::gl::STREAM_COPY,
1007 );
1008 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
1009 match check_gl_error("PboCreate", 0) {
1010 Ok(()) => Ok(id),
1011 Err(e) => {
1012 edgefirst_gl::gl::DeleteBuffers(1, &id);
1013 Err(e)
1014 }
1015 }
1016 }));
1017 reply_caught(result, resp, poisoned, "PboCreate");
1018}
1019
1020fn pbo_map_on_gl_thread(
1024 buffer_id: u32,
1025 size: usize,
1026) -> Result<edgefirst_tensor::PboMapping, Error> {
1027 let already = with_worker_mapped(|set| !set.insert(buffer_id)).unwrap_or(false);
1028 if already {
1029 return Err(Error::OpenGl(format!(
1030 "PBO buffer {buffer_id} is already mapped in this GL context \
1031 (a producer and a reconstructed cross-package consumer may \
1032 both hold a live tensor over it)"
1033 )));
1034 }
1035 let result = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
1036 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, buffer_id);
1037 let ptr = edgefirst_gl::gl::MapBufferRange(
1038 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
1039 0,
1040 size as isize,
1041 edgefirst_gl::gl::MAP_READ_BIT | edgefirst_gl::gl::MAP_WRITE_BIT,
1042 );
1043 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
1044 if ptr.is_null() {
1045 Err(Error::OpenGl("glMapBufferRange returned null".to_string()))
1046 } else {
1047 Ok(edgefirst_tensor::PboMapping {
1048 ptr: ptr as *mut u8,
1049 size,
1050 })
1051 }
1052 }));
1053 if !matches!(result, Ok(Ok(_))) {
1054 with_worker_mapped(|set| set.remove(&buffer_id));
1057 }
1058 match result {
1059 Ok(result) => result,
1060 Err(panic) => std::panic::resume_unwind(panic),
1063 }
1064}
1065
1066fn pbo_unmap_on_gl_thread(buffer_id: u32) -> Result<(), Error> {
1068 with_worker_mapped(|set| set.remove(&buffer_id));
1071 unsafe {
1072 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, buffer_id);
1073 let ok = edgefirst_gl::gl::UnmapBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER);
1074 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
1075 if ok == edgefirst_gl::gl::FALSE {
1076 Err(Error::OpenGl(
1077 "PBO data was corrupted during mapping".into(),
1078 ))
1079 } else {
1080 check_gl_error("PboUnmap", 0)
1081 }
1082 }
1083}
1084
1085fn handle_pbo_map(
1086 buffer_id: u32,
1087 size: usize,
1088 resp: tokio::sync::oneshot::Sender<Result<edgefirst_tensor::PboMapping, Error>>,
1089 poisoned: &mut bool,
1090) {
1091 let result =
1092 std::panic::catch_unwind(AssertUnwindSafe(|| pbo_map_on_gl_thread(buffer_id, size)));
1093 reply_caught(result, resp, poisoned, "PboMap");
1094}
1095
1096fn handle_pbo_unmap(
1097 buffer_id: u32,
1098 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
1099 poisoned: &mut bool,
1100) {
1101 let result = std::panic::catch_unwind(AssertUnwindSafe(|| pbo_unmap_on_gl_thread(buffer_id)));
1102 reply_caught(result, resp, poisoned, "PboUnmap");
1103}
1104
1105fn handle_pbo_delete(buffer_id: u32, poisoned: &mut bool) {
1106 with_worker_mapped(|set| set.remove(&buffer_id));
1113 if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
1114 edgefirst_gl::gl::DeleteBuffers(1, &buffer_id);
1115 })) {
1116 *poisoned = true;
1117 log::error!(
1118 "GL thread panicked during PboDelete: {}",
1119 panic_message(e.as_ref()),
1120 );
1121 }
1122}
1123
1124impl GLProcessorThreaded {
1125 pub fn new(kind: Option<EglDisplayKind>) -> Result<Self, Error> {
1128 Self::with_cache_capacity(kind, None)
1129 }
1130
1131 pub fn with_cache_capacity(
1137 kind: Option<EglDisplayKind>,
1138 capacity: Option<usize>,
1139 ) -> Result<Self, Error> {
1140 let worker_id = NEXT_GL_WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1141 let (send, recv) = tokio::sync::mpsc::channel::<GLProcessorMessage>(1);
1142
1143 let (create_ctx_send, create_ctx_recv) = tokio::sync::oneshot::channel();
1144
1145 let owned_pbos = std::sync::Arc::<super::processor::OwnedPbos>::default();
1146 let worker_owned = owned_pbos.clone();
1147 let handle = std::thread::spawn(move || {
1148 run_gl_worker(
1149 worker_id,
1150 worker_owned,
1151 kind,
1152 capacity,
1153 recv,
1154 create_ctx_send,
1155 );
1156 });
1157
1158 let caps = match create_ctx_recv.blocking_recv() {
1159 Ok(Err(e)) => return Err(e),
1160 Err(_) => {
1161 return Err(Error::Internal(
1162 "GL converter error messaging closed without update".to_string(),
1163 ));
1164 }
1165 Ok(Ok(caps)) => caps,
1166 };
1167
1168 Ok(Self {
1169 handle: Some(handle),
1170 sender: Some(send),
1171 worker_id,
1172 owned_pbos,
1173 caps,
1174 })
1175 }
1176}
1177
1178impl ImageProcessorTrait for GLProcessorThreaded {
1179 fn convert(
1180 &mut self,
1181 src: &TensorDyn,
1182 dst: &mut TensorDyn,
1183 options: Option<ConvertOptions>,
1184 ) -> crate::Result<()> {
1185 let options = options.unwrap_or_default();
1186 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1187 self.sender
1188 .as_ref()
1189 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1190 .blocking_send(GLProcessorMessage::ImageConvert(
1191 SendablePtr {
1192 ptr: NonNull::from(src),
1193 len: 1,
1194 },
1195 SendablePtr {
1196 ptr: NonNull::from(dst),
1197 len: 1,
1198 },
1199 options,
1200 false, err_send,
1202 ))
1203 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1204 err_recv.blocking_recv().map_err(|_| {
1205 Error::Internal("GL converter error messaging closed without update".to_string())
1206 })?
1207 }
1208
1209 fn convert_deferred(
1210 &mut self,
1211 src: &TensorDyn,
1212 dst: &mut TensorDyn,
1213 options: Option<ConvertOptions>,
1214 ) -> crate::Result<()> {
1215 let options = options.unwrap_or_default();
1216 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1217 self.sender
1218 .as_ref()
1219 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1220 .blocking_send(GLProcessorMessage::ImageConvert(
1221 SendablePtr {
1222 ptr: NonNull::from(src),
1223 len: 1,
1224 },
1225 SendablePtr {
1226 ptr: NonNull::from(dst),
1227 len: 1,
1228 },
1229 options,
1230 true, err_send,
1232 ))
1233 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1234 err_recv.blocking_recv().map_err(|_| {
1235 Error::Internal("GL converter error messaging closed without update".to_string())
1236 })?
1237 }
1238
1239 fn flush(&mut self) -> crate::Result<()> {
1240 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1241 self.sender
1242 .as_ref()
1243 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1244 .blocking_send(GLProcessorMessage::Flush(err_send))
1245 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1246 err_recv.blocking_recv().map_err(|_| {
1247 Error::Internal("GL converter error messaging closed without update".to_string())
1248 })?
1249 }
1250
1251 fn draw_decoded_masks(
1252 &mut self,
1253 dst: &mut TensorDyn,
1254 detect: &[crate::DetectBox],
1255 segmentation: &[crate::Segmentation],
1256 overlay: crate::MaskOverlay<'_>,
1257 ) -> crate::Result<()> {
1258 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1259 self.sender
1260 .as_ref()
1261 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1262 .blocking_send(GLProcessorMessage::DrawDecodedMasks(
1263 SendablePtr {
1264 ptr: NonNull::from(dst),
1265 len: 1,
1266 },
1267 SendablePtr {
1268 ptr: NonNull::new(detect.as_ptr() as *mut DetectBox).unwrap(),
1269 len: detect.len(),
1270 },
1271 SendablePtr {
1272 ptr: NonNull::new(segmentation.as_ptr() as *mut Segmentation).unwrap(),
1273 len: segmentation.len(),
1274 },
1275 overlay.opacity,
1276 overlay.background.map(|bg| SendablePtr {
1277 ptr: NonNull::from(bg).cast::<TensorDyn>(),
1278 len: 1,
1279 }),
1280 overlay.letterbox,
1281 overlay.color_mode,
1282 err_send,
1283 ))
1284 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1285 err_recv.blocking_recv().map_err(|_| {
1286 Error::Internal("GL converter error messaging closed without update".to_string())
1287 })?
1288 }
1289
1290 fn draw_proto_masks(
1291 &mut self,
1292 dst: &mut TensorDyn,
1293 detect: &[DetectBox],
1294 proto_data: &ProtoData,
1295 overlay: crate::MaskOverlay<'_>,
1296 ) -> crate::Result<()> {
1297 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1298 self.sender
1299 .as_ref()
1300 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1301 .blocking_send(GLProcessorMessage::DrawProtoMasks(
1302 SendablePtr {
1303 ptr: NonNull::from(dst),
1304 len: 1,
1305 },
1306 SendablePtr {
1307 ptr: NonNull::new(detect.as_ptr() as *mut DetectBox).unwrap(),
1308 len: detect.len(),
1309 },
1310 SendablePtr {
1311 ptr: NonNull::from(proto_data).cast::<ProtoData>(),
1312 len: 1,
1313 },
1314 overlay.opacity,
1315 overlay.background.map(|bg| SendablePtr {
1316 ptr: NonNull::from(bg).cast::<TensorDyn>(),
1317 len: 1,
1318 }),
1319 overlay.letterbox,
1320 overlay.color_mode,
1321 err_send,
1322 ))
1323 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1324 err_recv.blocking_recv().map_err(|_| {
1325 Error::Internal("GL converter error messaging closed without update".to_string())
1326 })?
1327 }
1328
1329 fn set_class_colors(&mut self, colors: &[[u8; 4]]) -> Result<(), crate::Error> {
1330 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1331 self.sender
1332 .as_ref()
1333 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1334 .blocking_send(GLProcessorMessage::SetColors(colors.to_vec(), err_send))
1335 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1336 err_recv.blocking_recv().map_err(|_| {
1337 Error::Internal("GL converter error messaging closed without update".to_string())
1338 })?
1339 }
1340}
1341
1342impl GLProcessorThreaded {
1343 pub fn set_colorimetry_mode(&mut self, mode: crate::ColorimetryMode) -> Result<(), Error> {
1348 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1349 self.sender
1350 .as_ref()
1351 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1352 .blocking_send(GLProcessorMessage::SetColorimetryMode(mode, err_send))
1353 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1354 err_recv.blocking_recv().map_err(|_| {
1355 Error::Internal("GL converter error messaging closed without update".to_string())
1356 })?
1357 }
1358
1359 pub fn set_interpolation(&mut self, mode: crate::Interpolation) -> Result<(), Error> {
1361 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1362 self.sender
1363 .as_ref()
1364 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1365 .blocking_send(GLProcessorMessage::SetInterpolation(mode, err_send))
1366 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1367 err_recv.blocking_recv().map_err(|_| {
1368 Error::Internal("GL converter error messaging closed without update".to_string())
1369 })?
1370 }
1371
1372 #[cfg(test)]
1378 pub(crate) fn set_fail_dst_import(&mut self, fail: bool) -> Result<(), Error> {
1379 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1380 self.sender
1381 .as_ref()
1382 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1383 .blocking_send(GLProcessorMessage::SetFailDstImport(fail, err_send))
1384 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1385 err_recv.blocking_recv().map_err(|_| {
1386 Error::Internal("GL converter error messaging closed without update".to_string())
1387 })?
1388 }
1389
1390 pub fn set_int8_interpolation_mode(
1392 &mut self,
1393 mode: Int8InterpolationMode,
1394 ) -> Result<(), crate::Error> {
1395 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1396 self.sender
1397 .as_ref()
1398 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1399 .blocking_send(GLProcessorMessage::SetInt8Interpolation(mode, err_send))
1400 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1401 err_recv.blocking_recv().map_err(|_| {
1402 Error::Internal("GL converter error messaging closed without update".to_string())
1403 })?
1404 }
1405
1406 pub fn egl_cache_stats(&self) -> Result<super::cache::GlCacheStats, Error> {
1413 let (send, recv) = tokio::sync::oneshot::channel();
1414 self.sender
1415 .as_ref()
1416 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1417 .blocking_send(GLProcessorMessage::EglCacheStats(send))
1418 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1419 recv.blocking_recv().map_err(|_| {
1420 Error::Internal("GL converter error messaging closed without update".to_string())
1421 })?
1422 }
1423
1424 pub fn convert_with_fence(
1435 &mut self,
1436 src: &TensorDyn,
1437 dst: &mut TensorDyn,
1438 options: Option<ConvertOptions>,
1439 ) -> Result<Option<super::CompletionFence>, Error> {
1440 let options = options.unwrap_or_default();
1441 use crate::ImageProcessorTrait as _;
1442 if !self.caps.native_fence_sync {
1443 self.convert(src, dst, Some(options))?;
1446 return Ok(None);
1447 }
1448 let (send, recv) = tokio::sync::oneshot::channel();
1449 self.sender
1450 .as_ref()
1451 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1452 .blocking_send(GLProcessorMessage::ImageConvertFenced(
1453 SendablePtr {
1454 ptr: NonNull::from(src),
1455 len: 1,
1456 },
1457 SendablePtr {
1458 ptr: NonNull::from(dst),
1459 len: 1,
1460 },
1461 options,
1462 send,
1463 ))
1464 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1465 recv.blocking_recv().map_err(|_| {
1466 Error::Internal("GL converter error messaging closed without update".to_string())
1467 })?
1468 }
1469
1470 pub fn convert_stats(&self) -> Result<super::cache::ConvertStats, Error> {
1475 let (send, recv) = tokio::sync::oneshot::channel();
1476 self.sender
1477 .as_ref()
1478 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1479 .blocking_send(GLProcessorMessage::ConvertStats(send))
1480 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1481 recv.blocking_recv().map_err(|_| {
1482 Error::Internal("GL converter error messaging closed without update".to_string())
1483 })?
1484 }
1485
1486 pub fn create_pbo_image(
1492 &self,
1493 width: usize,
1494 height: usize,
1495 format: edgefirst_tensor::PixelFormat,
1496 ) -> Result<edgefirst_tensor::Tensor<u8>, Error> {
1497 let sender = self
1498 .sender
1499 .as_ref()
1500 .ok_or(Error::OpenGl("GL processor is shutting down".to_string()))?;
1501
1502 let size = pbo_elem_count(width, height, format)
1503 .filter(|&s| s != 0)
1504 .ok_or_else(|| Error::OpenGl("Invalid image dimensions".to_string()))?;
1505
1506 let (tx, rx) = tokio::sync::oneshot::channel();
1508 sender
1509 .blocking_send(GLProcessorMessage::PboCreate(size, tx))
1510 .map_err(|_| Error::OpenGl("GL thread channel closed".to_string()))?;
1511 let buffer_id = rx
1512 .blocking_recv()
1513 .map_err(|_| Error::OpenGl("GL thread did not respond".to_string()))??;
1514
1515 let ops: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1516 sender: sender.downgrade(),
1517 worker_id: self.worker_id,
1518 });
1519 let owned_ops = ops.clone();
1520
1521 let shape = pbo_shape(width, height, format);
1522
1523 let pbo_tensor =
1524 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops)
1525 .map_err(|e| Error::OpenGl(format!("PBO tensor creation failed: {e:?}")))?;
1526 let mut tensor = edgefirst_tensor::Tensor::from_pbo(pbo_tensor)
1527 .map_err(|e| Error::OpenGl(format!("PBO tensor wrap failed: {e:?}")))?;
1528 tensor
1529 .set_format(format)
1530 .map_err(|e| Error::OpenGl(format!("Failed to set format on PBO tensor: {e:?}")))?;
1531 register_pbo_cuda(&mut tensor, buffer_id, size, sender);
1538 self.owned_pbos.insert(tensor.pbo_vtable_ptr(), &owned_ops);
1539 Ok(tensor)
1540 }
1541
1542 pub(crate) fn create_pbo_image_dtype(
1548 &self,
1549 width: usize,
1550 height: usize,
1551 format: edgefirst_tensor::PixelFormat,
1552 dtype: edgefirst_tensor::DType,
1553 ) -> Result<TensorDyn, Error> {
1554 let sender = self
1555 .sender
1556 .as_ref()
1557 .ok_or(Error::OpenGl("GL processor is shutting down".to_string()))?;
1558
1559 let elems = pbo_elem_count(width, height, format)
1560 .filter(|&e| e != 0)
1561 .ok_or_else(|| Error::OpenGl("Invalid image dimensions".to_string()))?;
1562
1563 let size = elems
1564 .checked_mul(dtype.size())
1565 .ok_or_else(|| Error::OpenGl("PBO size overflow".to_string()))?;
1566
1567 let (tx, rx) = tokio::sync::oneshot::channel();
1569 sender
1570 .blocking_send(GLProcessorMessage::PboCreate(size, tx))
1571 .map_err(|_| Error::OpenGl("GL thread channel closed".to_string()))?;
1572 let buffer_id = rx
1573 .blocking_recv()
1574 .map_err(|_| Error::OpenGl("GL thread did not respond".to_string()))??;
1575
1576 let ops: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1577 sender: sender.downgrade(),
1578 worker_id: self.worker_id,
1579 });
1580 let owned_ops = ops.clone();
1581
1582 let shape = pbo_shape(width, height, format);
1583
1584 let map_err = |e: edgefirst_tensor::Error| {
1585 Error::OpenGl(format!("PBO tensor creation failed: {e:?}"))
1586 };
1587 let set_err = |e: edgefirst_tensor::Error| {
1588 Error::OpenGl(format!("Failed to set format on PBO tensor: {e:?}"))
1589 };
1590
1591 let tensor = match dtype {
1592 edgefirst_tensor::DType::U8 => {
1593 let pbo =
1594 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops)
1595 .map_err(map_err)?;
1596 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo).map_err(map_err)?;
1597 t.set_format(format).map_err(set_err)?;
1598 register_pbo_cuda(&mut t, buffer_id, size, sender);
1599 TensorDyn::from(t)
1600 }
1601 edgefirst_tensor::DType::F16 => {
1602 let pbo = edgefirst_tensor::PboTensor::<edgefirst_tensor::f16>::from_pbo(
1603 buffer_id, size, &shape, None, ops,
1604 )
1605 .map_err(map_err)?;
1606 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo).map_err(map_err)?;
1607 t.set_format(format).map_err(set_err)?;
1608 register_pbo_cuda(&mut t, buffer_id, size, sender);
1609 TensorDyn::from(t)
1610 }
1611 edgefirst_tensor::DType::F32 => {
1612 let pbo = edgefirst_tensor::PboTensor::<f32>::from_pbo(
1613 buffer_id, size, &shape, None, ops,
1614 )
1615 .map_err(map_err)?;
1616 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo).map_err(map_err)?;
1617 t.set_format(format).map_err(set_err)?;
1618 register_pbo_cuda(&mut t, buffer_id, size, sender);
1619 TensorDyn::from(t)
1620 }
1621 other => return Err(Error::OpenGl(format!("unsupported PBO dtype {other:?}"))),
1622 };
1623 self.owned_pbos.insert(tensor.pbo_vtable_ptr(), &owned_ops);
1624 Ok(tensor)
1625 }
1626
1627 pub(crate) fn transfer_backend(&self) -> TransferBackend {
1629 self.caps.transfer_backend
1630 }
1631
1632 pub(crate) fn supported_render_dtypes(&self) -> crate::RenderDtypeSupport {
1639 self.caps.render_dtypes
1640 }
1641
1642 pub(crate) fn dst_cpu_access(
1647 &self,
1648 memory: edgefirst_tensor::TensorMemory,
1649 format: edgefirst_tensor::PixelFormat,
1650 dtype: edgefirst_tensor::DType,
1651 ) -> Option<edgefirst_tensor::CpuAccess> {
1652 use edgefirst_tensor::{CpuAccess, DType, PixelFormat, TensorMemory};
1653 let renders = match dtype {
1654 DType::U8 | DType::I8 => GLProcessorST::dst_format_renderable(
1655 self.caps.transfer_backend,
1656 memory,
1657 format,
1658 self.caps.has_bgra,
1659 ),
1660 DType::F16 | DType::F32 => {
1661 super::float_dispatch::classify_float_render(
1662 PixelFormat::Rgba,
1663 format,
1664 dtype,
1665 memory,
1666 self.caps.render_dtypes,
1667 <super::platform::Platform as super::platform::GlPlatform>::ZERO_COPY_FLOAT,
1668 ) != super::float_dispatch::FloatRenderPath::None
1669 }
1670 _ => false,
1671 };
1672 let readback = matches!(memory, TensorMemory::Mem | TensorMemory::Shm);
1673 renders.then_some(if readback {
1674 CpuAccess::Write
1675 } else {
1676 CpuAccess::None
1677 })
1678 }
1679}
1680
1681impl Drop for GLProcessorThreaded {
1682 fn drop(&mut self) {
1683 drop(self.sender.take());
1689 let _ = self.handle.take().and_then(|h| h.join().ok());
1690 }
1691}
1692
1693#[cfg(test)]
1697#[cfg_attr(coverage_nightly, coverage(off))]
1698mod tests {
1699 use super::{pbo_elem_count, pbo_shape, GLProcessorThreaded, GlPboOps};
1700 use edgefirst_tensor::{PixelFormat, TensorTrait};
1701
1702 #[test]
1703 fn elem_count_per_format() {
1704 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Rgba), Some(8 * 4 * 4));
1706 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Rgb), Some(8 * 4 * 3));
1708 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Nv12), Some(8 * 4 * 3 / 2));
1710 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Nv16), Some(8 * 4 * 2));
1712 }
1713
1714 #[test]
1715 fn elem_count_overflow_is_none() {
1716 assert_eq!(pbo_elem_count(usize::MAX, 2, PixelFormat::Rgba), None);
1718 assert_eq!(pbo_elem_count(usize::MAX, 1, PixelFormat::Rgb), None);
1720 }
1721
1722 #[test]
1723 fn shape_per_format() {
1724 assert_eq!(pbo_shape(8, 4, PixelFormat::PlanarRgb), vec![3, 4, 8]);
1726 assert_eq!(pbo_shape(8, 4, PixelFormat::Nv12), vec![4 * 3 / 2, 8]);
1728 assert_eq!(pbo_shape(8, 4, PixelFormat::Nv16), vec![4 * 2, 8]);
1730 assert_eq!(pbo_shape(8, 4, PixelFormat::Rgba), vec![4, 8, 4]);
1732 }
1733
1734 #[test]
1752 #[cfg(any(target_os = "linux", target_os = "windows"))] fn concurrent_maps_from_two_independent_handles_on_one_buffer_do_not_corrupt_gl_state() {
1754 let gl = match GLProcessorThreaded::new(None) {
1755 Ok(gl) => gl,
1756 Err(e) => {
1757 crate::test_support::report_skip(&format!(
1758 "concurrent_maps_from_two_independent_handles... - \
1759 OpenGL not available: {e:?}"
1760 ));
1761 return;
1762 }
1763 };
1764 let tensor_a = gl
1765 .create_pbo_image(64, 64, PixelFormat::Rgba)
1766 .expect("a PBO on a live GL context");
1767 let buffer_id = tensor_a.pbo_id().expect("just created a PBO tensor");
1768 let size = tensor_a.capacity_bytes();
1769 let shape = tensor_a.shape().to_vec();
1770
1771 let ops_b: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1775 sender: gl.sender.as_ref().unwrap().downgrade(),
1776 worker_id: gl.worker_id,
1777 });
1778 let tensor_b =
1779 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops_b)
1780 .expect("from_pbo against a real, already-created buffer_id must succeed");
1781
1782 let map_a = tensor_a.map().expect("first map must succeed");
1784
1785 let map_b_result = tensor_b.map();
1789 let Err(map_b_err) = map_b_result else {
1797 panic!(
1798 "a second, independent handle mapping the SAME real buffer while the \
1799 first mapping is still open must be refused, not silently succeed"
1800 );
1801 };
1802 let msg = format!("{map_b_err:?}");
1803 assert!(
1804 msg.contains("already mapped"),
1805 "must be refused specifically by the GL-thread-level reservation check \
1806 (mapped_pbo_buffers), not some other error path: {msg}"
1807 );
1808
1809 drop(map_a);
1810
1811 let map_b_after = tensor_b.map();
1815 assert!(
1816 map_b_after.is_ok(),
1817 "after the first handle's mapping releases, the second handle must be able to \
1818 map the same buffer: {map_b_after:?}"
1819 );
1820 }
1821}