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::{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 ImageConvertFenced(
31 SendablePtr<TensorDyn>,
32 SendablePtr<TensorDyn>,
33 Rotation,
34 Flip,
35 Crop,
36 tokio::sync::oneshot::Sender<Result<Option<super::CompletionFence>, Error>>,
37 ),
38 Flush(tokio::sync::oneshot::Sender<Result<(), Error>>),
40 SetColors(
41 Vec<[u8; 4]>,
42 tokio::sync::oneshot::Sender<Result<(), Error>>,
43 ),
44 DrawDecodedMasks(
45 SendablePtr<TensorDyn>,
46 SendablePtr<DetectBox>,
47 SendablePtr<Segmentation>,
48 f32, Option<SendablePtr<TensorDyn>>, Option<[f32; 4]>, crate::ColorMode,
52 tokio::sync::oneshot::Sender<Result<(), Error>>,
53 ),
54 DrawProtoMasks(
55 SendablePtr<TensorDyn>,
56 SendablePtr<DetectBox>,
57 SendablePtr<ProtoData>,
58 f32, Option<SendablePtr<TensorDyn>>, Option<[f32; 4]>, crate::ColorMode,
62 tokio::sync::oneshot::Sender<Result<(), Error>>,
63 ),
64 SetInt8Interpolation(
65 Int8InterpolationMode,
66 tokio::sync::oneshot::Sender<Result<(), Error>>,
67 ),
68 SetColorimetryMode(
70 crate::ColorimetryMode,
71 tokio::sync::oneshot::Sender<Result<(), Error>>,
72 ),
73 #[cfg(test)]
75 SetFailDstImport(bool, tokio::sync::oneshot::Sender<Result<(), Error>>),
76 EglCacheStats(tokio::sync::oneshot::Sender<Result<super::cache::GlCacheStats, Error>>),
78 ConvertStats(tokio::sync::oneshot::Sender<Result<super::cache::ConvertStats, Error>>),
80 PboCreate(
81 usize, tokio::sync::oneshot::Sender<Result<u32, Error>>,
83 ),
84 PboMap(
85 u32, usize, tokio::sync::oneshot::Sender<Result<edgefirst_tensor::PboMapping, Error>>,
88 ),
89 PboUnmap(
90 u32, tokio::sync::oneshot::Sender<Result<(), Error>>,
92 ),
93 PboDelete(u32), CudaRegisterBuffer(u32, tokio::sync::oneshot::Sender<Option<usize>>),
95 CudaMap(usize, tokio::sync::oneshot::Sender<Option<(usize, usize)>>),
96 CudaUnmap(usize), CudaUnregister(usize), }
99
100fn pbo_elem_count(
110 width: usize,
111 height: usize,
112 format: edgefirst_tensor::PixelFormat,
113) -> Option<usize> {
114 format
121 .allocation_shape(width, height)?
122 .iter()
123 .try_fold(1usize, |acc, &d| acc.checked_mul(d))
124}
125
126fn pbo_shape(width: usize, height: usize, format: edgefirst_tensor::PixelFormat) -> Vec<usize> {
130 format
131 .allocation_shape(width, height)
132 .unwrap_or_else(|| vec![height, width, format.channels()])
133}
134
135fn register_pbo_cuda<T>(
144 tensor: &mut edgefirst_tensor::Tensor<T>,
145 buffer_id: u32,
146 size: usize,
147 sender: &Sender<GLProcessorMessage>,
148) where
149 T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync + edgefirst_tensor::Element,
150{
151 if !edgefirst_tensor::is_cuda_available() {
152 return;
153 }
154 let (tx, rx) = tokio::sync::oneshot::channel();
155 if sender
156 .blocking_send(GLProcessorMessage::CudaRegisterBuffer(buffer_id, tx))
157 .is_ok()
158 {
159 if let Ok(Some(resource)) = rx.blocking_recv() {
160 let ops = std::sync::Arc::new(GlCudaOps {
161 sender: sender.downgrade(),
162 });
163 tensor.set_cuda_handle(edgefirst_tensor::CudaHandle::new_gl(
164 resource as *mut std::ffi::c_void,
165 size,
166 ops,
167 ));
168 }
169 }
170}
171
172struct GlPboOps {
179 sender: WeakSender<GLProcessorMessage>,
180 worker_id: u64,
185}
186
187static NEXT_GL_WORKER_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
189
190struct GlWorkerState {
192 id: u64,
193 serializes: bool,
196 mapped: std::collections::HashSet<u32>,
198}
199
200thread_local! {
201 static GL_WORKER: std::cell::RefCell<Option<GlWorkerState>> =
217 const { std::cell::RefCell::new(None) };
218}
219
220fn with_worker_mapped<R>(f: impl FnOnce(&mut std::collections::HashSet<u32>) -> R) -> Option<R> {
224 GL_WORKER.with(|c| c.borrow_mut().as_mut().map(|s| f(&mut s.mapped)))
225}
226
227fn on_owning_gl_worker(worker_id: u64) -> bool {
229 GL_WORKER.with(|c| matches!(*c.borrow(), Some(ref s) if s.id == worker_id))
230}
231
232fn on_serializing_gl_worker() -> bool {
235 GL_WORKER.with(|c| matches!(*c.borrow(), Some(ref s) if s.serializes))
236}
237
238fn cross_context_pbo_error(op: &str, buffer_id: u32) -> edgefirst_tensor::Error {
242 edgefirst_tensor::Error::InvalidOperation(format!(
243 "PBO {buffer_id}: cannot {op} a buffer owned by another GL context while \
244 this one holds the serialization lock; convert it through the processor \
245 that created it"
246 ))
247}
248
249thread_local! {
250 static GL_WORKER_POISON: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
253}
254
255fn inline_gl_op<T>(what: &str, f: impl FnOnce() -> Result<T, Error>) -> Result<T, Error> {
263 match std::panic::catch_unwind(AssertUnwindSafe(f)) {
264 Ok(result) => result,
265 Err(e) => {
266 GL_WORKER_POISON.with(|p| p.set(true));
267 Err(Error::Internal(format!(
268 "GL thread panicked during {what}: {}",
269 panic_message(e.as_ref()),
270 )))
271 }
272 }
273}
274
275unsafe impl edgefirst_tensor::PboOps for GlPboOps {
281 fn map_buffer(
282 &self,
283 buffer_id: u32,
284 size: usize,
285 ) -> edgefirst_tensor::Result<edgefirst_tensor::PboMapping> {
286 if on_owning_gl_worker(self.worker_id) {
291 return inline_gl_op("PboMap", || pbo_map_on_gl_thread(buffer_id, size)).map_err(|e| {
292 edgefirst_tensor::Error::NotImplemented(format!("GL PBO map failed: {e:?}"))
293 });
294 }
295 if on_serializing_gl_worker() {
297 return Err(cross_context_pbo_error("map", buffer_id));
298 }
299 let sender = self
300 .sender
301 .upgrade()
302 .ok_or(edgefirst_tensor::Error::PboDisconnected)?;
303 let (tx, rx) = tokio::sync::oneshot::channel();
304 sender
305 .blocking_send(GLProcessorMessage::PboMap(buffer_id, size, tx))
306 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?;
307 rx.blocking_recv()
308 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?
309 .map_err(|e| {
310 edgefirst_tensor::Error::NotImplemented(format!("GL PBO map failed: {e:?}"))
311 })
312 }
313
314 fn unmap_buffer(&self, buffer_id: u32) -> edgefirst_tensor::Result<()> {
315 if on_owning_gl_worker(self.worker_id) {
317 return inline_gl_op("PboUnmap", || pbo_unmap_on_gl_thread(buffer_id)).map_err(|e| {
318 edgefirst_tensor::Error::NotImplemented(format!("GL PBO unmap failed: {e:?}"))
319 });
320 }
321 if on_serializing_gl_worker() {
323 return Err(cross_context_pbo_error("unmap", buffer_id));
324 }
325 let sender = self
326 .sender
327 .upgrade()
328 .ok_or(edgefirst_tensor::Error::PboDisconnected)?;
329 let (tx, rx) = tokio::sync::oneshot::channel();
330 sender
331 .blocking_send(GLProcessorMessage::PboUnmap(buffer_id, tx))
332 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?;
333 rx.blocking_recv()
334 .map_err(|_| edgefirst_tensor::Error::PboDisconnected)?
335 .map_err(|e| {
336 edgefirst_tensor::Error::NotImplemented(format!("GL PBO unmap failed: {e:?}"))
337 })
338 }
339
340 fn delete_buffer(&self, buffer_id: u32) {
341 if on_owning_gl_worker(self.worker_id) {
343 with_worker_mapped(|set| set.remove(&buffer_id));
344 let _ = inline_gl_op("PboDelete", || {
347 unsafe { edgefirst_gl::gl::DeleteBuffers(1, &buffer_id) };
348 Ok(())
349 });
350 return;
351 }
352 if let Some(sender) = self.sender.upgrade() {
353 if on_serializing_gl_worker() {
355 if sender
356 .try_send(GLProcessorMessage::PboDelete(buffer_id))
357 .is_err()
358 {
359 log::warn!(
360 "PBO {buffer_id}: cross-context delete skipped to avoid a deadlock; \
361 the buffer is freed when its context is destroyed"
362 );
363 }
364 return;
365 }
366 let _ = sender.blocking_send(GLProcessorMessage::PboDelete(buffer_id));
367 }
368 }
369}
370
371struct GlCudaOps {
378 sender: WeakSender<GLProcessorMessage>,
379}
380
381impl edgefirst_tensor::CudaGlOps for GlCudaOps {
382 fn map(&self, resource: *mut std::ffi::c_void) -> Option<(*mut std::ffi::c_void, usize)> {
383 let sender = self.sender.upgrade()?;
384 let (tx, rx) = tokio::sync::oneshot::channel();
385 sender
386 .blocking_send(GLProcessorMessage::CudaMap(resource as usize, tx))
387 .ok()?;
388 rx.blocking_recv()
389 .ok()?
390 .map(|(p, l)| (p as *mut std::ffi::c_void, l))
391 }
392
393 fn unmap(&self, resource: *mut std::ffi::c_void) {
394 if let Some(sender) = self.sender.upgrade() {
395 let _ = sender.blocking_send(GLProcessorMessage::CudaUnmap(resource as usize));
396 }
397 }
398
399 fn unregister(&self, resource: *mut std::ffi::c_void) {
400 if let Some(sender) = self.sender.upgrade() {
401 let _ = sender.blocking_send(GLProcessorMessage::CudaUnregister(resource as usize));
402 }
403 }
404}
405
406#[derive(Debug)]
411pub struct GLProcessorThreaded {
412 handle: Option<JoinHandle<()>>,
414
415 sender: Option<Sender<GLProcessorMessage>>,
417 worker_id: u64,
420 owned_pbos: std::sync::Arc<super::processor::OwnedPbos>,
423 caps: super::platform::PlatformCaps,
427}
428
429unsafe impl Send for GLProcessorThreaded {}
430unsafe impl Sync for GLProcessorThreaded {}
431
432struct SendablePtr<T: Send> {
433 ptr: NonNull<T>,
434 len: usize,
435}
436
437unsafe impl<T> Send for SendablePtr<T> where T: Send {}
438
439fn panic_message(info: &(dyn std::any::Any + Send)) -> String {
441 if let Some(s) = info.downcast_ref::<&str>() {
442 s.to_string()
443 } else if let Some(s) = info.downcast_ref::<String>() {
444 s.clone()
445 } else {
446 "unknown panic".to_string()
447 }
448}
449
450fn reply_caught<T>(
451 result: std::thread::Result<crate::Result<T>>,
452 resp: tokio::sync::oneshot::Sender<crate::Result<T>>,
453 poisoned: &mut bool,
454 what: &str,
455) {
456 let _ = resp.send(match result {
457 Ok(res) => res,
458 Err(e) => {
459 *poisoned = true;
460 Err(crate::Error::Internal(format!(
461 "GL thread panicked during {what}: {}",
462 panic_message(e.as_ref()),
463 )))
464 }
465 });
466}
467
468fn reject_poisoned_message(msg: GLProcessorMessage) {
469 let poison_err =
470 crate::Error::Internal("GL context is poisoned after a prior panic".to_string());
471 match msg {
472 GLProcessorMessage::ImageConvertFenced(.., resp) => {
473 let _ = resp.send(Err(poison_err));
474 }
475 GLProcessorMessage::ImageConvert(.., resp) => {
476 let _ = resp.send(Err(poison_err));
477 }
478 GLProcessorMessage::Flush(resp) => {
479 let _ = resp.send(Err(poison_err));
480 }
481 GLProcessorMessage::DrawDecodedMasks(.., resp) => {
482 let _ = resp.send(Err(poison_err));
483 }
484 GLProcessorMessage::DrawProtoMasks(.., resp) => {
485 let _ = resp.send(Err(poison_err));
486 }
487 GLProcessorMessage::SetColors(_, resp) => {
488 let _ = resp.send(Err(poison_err));
489 }
490 GLProcessorMessage::SetInt8Interpolation(_, resp) => {
491 let _ = resp.send(Err(poison_err));
492 }
493 GLProcessorMessage::SetColorimetryMode(_, resp) => {
494 let _ = resp.send(Err(poison_err));
495 }
496 #[cfg(test)]
497 GLProcessorMessage::SetFailDstImport(_, resp) => {
498 let _ = resp.send(Err(poison_err));
499 }
500 GLProcessorMessage::EglCacheStats(resp) => {
501 let _ = resp.send(Err(poison_err));
502 }
503 GLProcessorMessage::ConvertStats(resp) => {
504 let _ = resp.send(Err(poison_err));
505 }
506 GLProcessorMessage::PboCreate(_, resp) => {
507 let _ = resp.send(Err(poison_err));
508 }
509 GLProcessorMessage::PboMap(_, _, resp) => {
510 let _ = resp.send(Err(poison_err));
511 }
512 GLProcessorMessage::PboUnmap(_, resp) => {
513 let _ = resp.send(Err(poison_err));
514 }
515 GLProcessorMessage::PboDelete(_) => {}
516 GLProcessorMessage::CudaRegisterBuffer(_, resp) => {
517 let _ = resp.send(None);
518 }
519 GLProcessorMessage::CudaMap(_, resp) => {
520 let _ = resp.send(None);
521 }
522 GLProcessorMessage::CudaUnmap(_) => {}
526 GLProcessorMessage::CudaUnregister(_) => {}
527 }
528}
529
530fn run_gl_worker(
531 worker_id: u64,
532 owned_pbos: std::sync::Arc<super::processor::OwnedPbos>,
533 kind: Option<EglDisplayKind>,
534 capacity: Option<usize>,
535 mut recv: tokio::sync::mpsc::Receiver<GLProcessorMessage>,
536 create_ctx_send: tokio::sync::oneshot::Sender<Result<super::platform::PlatformCaps, Error>>,
537) {
538 #[cfg(target_os = "linux")]
543 let init_result = {
544 let _guard = super::context::GL_MUTEX
545 .lock()
546 .unwrap_or_else(|e| e.into_inner());
547 GLProcessorST::new(kind, capacity)
548 };
549 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
553 let init_result = GLProcessorST::new(kind, capacity);
554 #[cfg(target_os = "windows")]
562 let init_result = {
563 let _guard = NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
564 GLProcessorST::new(kind, capacity)
565 };
566 let mut gl_converter = match init_result {
567 Ok(gl) => gl,
568 Err(e) => {
569 let _ = create_ctx_send.send(Err(e));
570 return;
571 }
572 };
573 gl_converter.owned_pbos = owned_pbos;
574 let caps = gl_converter.platform_caps();
577 let _ = create_ctx_send.send(Ok(caps));
578 let mut poisoned = false;
579 GL_WORKER.with(|c| {
601 *c.borrow_mut() = Some(GlWorkerState {
602 id: worker_id,
603 serializes: false,
605 mapped: std::collections::HashSet::new(),
606 });
607 });
608 struct GlWorkerSlot;
611 impl Drop for GlWorkerSlot {
612 fn drop(&mut self) {
613 GL_WORKER.with(|c| *c.borrow_mut() = None);
614 GL_WORKER_POISON.with(|p| p.set(false));
615 }
616 }
617 let _gl_worker_slot = GlWorkerSlot;
618 let serialize_per_msg = match std::env::var("EDGEFIRST_GL_SERIALIZE").as_deref() {
627 Ok("full") => true,
628 Ok("lifecycle") => {
629 #[cfg(target_os = "windows")]
634 log::warn!(
635 "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"
636 );
637 false
638 }
639 _ => caps.serialize_gl,
640 };
641 log::debug!(
642 "GL serialization policy: {}",
643 if serialize_per_msg {
644 "Full (per-message global lock)"
645 } else {
646 "LifecycleOnly (parallel across processors)"
647 }
648 );
649 GL_WORKER.with(|c| {
650 if let Some(state) = c.borrow_mut().as_mut() {
651 state.serializes = serialize_per_msg;
652 }
653 });
654 while let Some(msg) = recv.blocking_recv() {
655 #[cfg(target_os = "linux")]
664 let _guard = serialize_per_msg.then(|| {
665 super::context::GL_MUTEX
666 .lock()
667 .unwrap_or_else(|e| e.into_inner())
668 });
669 #[cfg(any(
670 target_os = "macos",
671 target_os = "ios",
672 target_os = "android",
673 target_os = "windows"
674 ))]
675 let _guard =
676 serialize_per_msg.then(|| NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner()));
677
678 if poisoned {
683 reject_poisoned_message(msg);
684 continue;
685 }
686
687 gl_converter.begin_gpu_pass();
691 handle_gl_message(msg, &mut gl_converter, &mut poisoned);
692 poisoned |= GL_WORKER_POISON.with(|p| p.replace(false));
696 gl_converter.end_gpu_pass_if_synced();
700 }
701 #[cfg(target_os = "linux")]
705 let _guard = super::context::GL_MUTEX
706 .lock()
707 .unwrap_or_else(|e| e.into_inner());
708 #[cfg(target_os = "windows")]
709 let _guard = NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
710 drop(gl_converter);
711}
712
713#[cfg(not(target_os = "linux"))]
717static NON_LINUX_GL_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
718
719#[cfg(target_os = "windows")]
730pub(super) fn lifecycle_guard() -> std::sync::MutexGuard<'static, ()> {
731 NON_LINUX_GL_MUTEX.lock().unwrap_or_else(|e| e.into_inner())
732}
733
734fn handle_gl_message(
735 msg: GLProcessorMessage,
736 gl_converter: &mut GLProcessorST,
737 poisoned: &mut bool,
738) {
739 match msg {
740 GLProcessorMessage::ImageConvert(src, dst, rotation, flip, crop, defer, resp) => {
741 handle_image_convert(
742 src,
743 dst,
744 (rotation, flip, crop, defer),
745 resp,
746 gl_converter,
747 poisoned,
748 );
749 }
750 GLProcessorMessage::ImageConvertFenced(src, dst, rotation, flip, crop, resp) => {
751 handle_image_convert_fenced(
752 src,
753 dst,
754 (rotation, flip, crop),
755 resp,
756 gl_converter,
757 poisoned,
758 );
759 }
760 GLProcessorMessage::Flush(resp) => {
761 let result = std::panic::catch_unwind(AssertUnwindSafe(|| gl_converter.flush()));
762 reply_caught(result, resp, poisoned, "Flush");
763 }
764 GLProcessorMessage::DrawDecodedMasks(
765 dst,
766 det,
767 seg,
768 opacity,
769 bg,
770 letterbox,
771 color_mode,
772 resp,
773 ) => {
774 handle_draw_decoded_masks(
775 dst,
776 det,
777 seg,
778 (opacity, bg, letterbox, color_mode),
779 resp,
780 gl_converter,
781 poisoned,
782 );
783 }
784 GLProcessorMessage::DrawProtoMasks(
785 dst,
786 det,
787 proto_data,
788 opacity,
789 bg,
790 letterbox,
791 color_mode,
792 resp,
793 ) => {
794 handle_draw_proto_masks(
795 dst,
796 det,
797 proto_data,
798 (opacity, bg, letterbox, color_mode),
799 resp,
800 gl_converter,
801 poisoned,
802 );
803 }
804 GLProcessorMessage::SetColors(colors, resp) => {
805 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
806 gl_converter.set_class_colors(&colors)
807 }));
808 reply_caught(result, resp, poisoned, "SetColors");
809 }
810 GLProcessorMessage::SetColorimetryMode(mode, resp) => {
811 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
812 gl_converter.set_colorimetry_mode(mode);
813 Ok(())
814 }));
815 reply_caught(result, resp, poisoned, "SetColorimetryMode");
816 }
817 GLProcessorMessage::SetInt8Interpolation(mode, resp) => {
818 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
819 gl_converter.set_int8_interpolation_mode(mode);
820 Ok(())
821 }));
822 reply_caught(result, resp, poisoned, "SetInt8Interpolation");
823 }
824 #[cfg(test)]
825 GLProcessorMessage::SetFailDstImport(fail, resp) => {
826 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
827 gl_converter.fail_dst_import = fail;
828 Ok(())
829 }));
830 reply_caught(result, resp, poisoned, "SetFailDstImport");
831 }
832 GLProcessorMessage::EglCacheStats(resp) => {
833 let result =
834 std::panic::catch_unwind(AssertUnwindSafe(|| Ok(gl_converter.egl_cache_stats())));
835 reply_caught(result, resp, poisoned, "EglCacheStats");
836 }
837 GLProcessorMessage::ConvertStats(resp) => {
838 let result =
839 std::panic::catch_unwind(AssertUnwindSafe(|| Ok(gl_converter.convert_stats())));
840 reply_caught(result, resp, poisoned, "ConvertStats");
841 }
842 GLProcessorMessage::PboCreate(size, resp) => {
843 handle_pbo_create(size, resp, poisoned);
844 }
845 GLProcessorMessage::PboMap(buffer_id, size, resp) => {
846 handle_pbo_map(buffer_id, size, resp, poisoned);
847 }
848 GLProcessorMessage::PboUnmap(buffer_id, resp) => {
849 handle_pbo_unmap(buffer_id, resp, poisoned);
850 }
851 GLProcessorMessage::PboDelete(buffer_id) => {
852 handle_pbo_delete(buffer_id, poisoned);
853 }
854 GLProcessorMessage::CudaRegisterBuffer(buffer_id, resp) => {
855 let _ = resp.send(edgefirst_tensor::gl_register_buffer(buffer_id));
857 }
858 GLProcessorMessage::CudaMap(resource, resp) => {
859 gl_converter.flush_pending();
866 let _ = resp.send(edgefirst_tensor::gl_map_resource(resource));
867 }
868 GLProcessorMessage::CudaUnmap(resource) => {
869 edgefirst_tensor::gl_unmap_resource(resource);
870 }
871 GLProcessorMessage::CudaUnregister(resource) => {
872 edgefirst_tensor::gl_unregister_resource(resource);
873 }
874 }
875}
876
877fn handle_image_convert(
878 src: SendablePtr<TensorDyn>,
879 mut dst: SendablePtr<TensorDyn>,
880 geom: (Rotation, Flip, Crop, bool),
881 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
882 gl_converter: &mut GLProcessorST,
883 poisoned: &mut bool,
884) {
885 let (rotation, flip, crop, defer) = geom;
886 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
889 let src = unsafe { src.ptr.as_ref() };
890 let dst = unsafe { dst.ptr.as_mut() };
891 if defer {
895 gl_converter.convert_deferred(src, dst, rotation, flip, crop)
896 } else {
897 gl_converter.convert(src, dst, rotation, flip, crop)
898 }
899 }));
900 reply_caught(result, resp, poisoned, "ImageConvert");
901}
902
903fn handle_image_convert_fenced(
904 src: SendablePtr<TensorDyn>,
905 mut dst: SendablePtr<TensorDyn>,
906 geom: (Rotation, Flip, Crop),
907 resp: tokio::sync::oneshot::Sender<Result<Option<super::CompletionFence>, Error>>,
908 gl_converter: &mut GLProcessorST,
909 poisoned: &mut bool,
910) {
911 let (rotation, flip, crop) = geom;
912 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
915 let src = unsafe { src.ptr.as_ref() };
916 let dst = unsafe { dst.ptr.as_mut() };
917 gl_converter.convert_fenced(src, dst, rotation, flip, crop)
918 }));
919 reply_caught(result, resp, poisoned, "ImageConvertFenced");
920}
921
922fn handle_draw_decoded_masks(
923 mut dst: SendablePtr<TensorDyn>,
924 det: SendablePtr<DetectBox>,
925 seg: SendablePtr<Segmentation>,
926 overlay: (
927 f32,
928 Option<SendablePtr<TensorDyn>>,
929 Option<[f32; 4]>,
930 crate::ColorMode,
931 ),
932 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
933 gl_converter: &mut GLProcessorST,
934 poisoned: &mut bool,
935) {
936 let (opacity, bg, letterbox, color_mode) = overlay;
937 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
941 let dst = unsafe { dst.ptr.as_mut() };
942 let det = unsafe { std::slice::from_raw_parts(det.ptr.as_ptr(), det.len) };
943 let seg = unsafe { std::slice::from_raw_parts(seg.ptr.as_ptr(), seg.len) };
944 let bg_ref = bg.map(|p| unsafe { &*p.ptr.as_ptr() });
945 gl_converter.draw_decoded_masks(
946 dst,
947 det,
948 seg,
949 crate::MaskOverlay {
950 background: bg_ref,
951 opacity,
952 letterbox,
953 color_mode,
954 },
955 )
956 }));
957 reply_caught(result, resp, poisoned, "DrawDecodedMasks");
958}
959
960fn handle_draw_proto_masks(
961 mut dst: SendablePtr<TensorDyn>,
962 det: SendablePtr<DetectBox>,
963 proto_data: SendablePtr<ProtoData>,
964 overlay: (
965 f32,
966 Option<SendablePtr<TensorDyn>>,
967 Option<[f32; 4]>,
968 crate::ColorMode,
969 ),
970 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
971 gl_converter: &mut GLProcessorST,
972 poisoned: &mut bool,
973) {
974 let (opacity, bg, letterbox, color_mode) = overlay;
975 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
978 let dst = unsafe { dst.ptr.as_mut() };
979 let det = unsafe { std::slice::from_raw_parts(det.ptr.as_ptr(), det.len) };
980 let bg_ref = bg.map(|p| unsafe { &*p.ptr.as_ptr() });
981 let proto_data = unsafe { proto_data.ptr.as_ref() };
982 gl_converter.draw_proto_masks(
983 dst,
984 det,
985 proto_data,
986 crate::MaskOverlay {
987 background: bg_ref,
988 opacity,
989 letterbox,
990 color_mode,
991 },
992 )
993 }));
994 reply_caught(result, resp, poisoned, "DrawProtoMasks");
995}
996
997fn handle_pbo_create(
998 size: usize,
999 resp: tokio::sync::oneshot::Sender<Result<u32, Error>>,
1000 poisoned: &mut bool,
1001) {
1002 let result = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
1003 let mut id: u32 = 0;
1004 edgefirst_gl::gl::GenBuffers(1, &mut id);
1005 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, id);
1006 edgefirst_gl::gl::BufferData(
1007 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
1008 size as isize,
1009 std::ptr::null(),
1010 edgefirst_gl::gl::STREAM_COPY,
1011 );
1012 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
1013 match check_gl_error("PboCreate", 0) {
1014 Ok(()) => Ok(id),
1015 Err(e) => {
1016 edgefirst_gl::gl::DeleteBuffers(1, &id);
1017 Err(e)
1018 }
1019 }
1020 }));
1021 reply_caught(result, resp, poisoned, "PboCreate");
1022}
1023
1024fn pbo_map_on_gl_thread(
1028 buffer_id: u32,
1029 size: usize,
1030) -> Result<edgefirst_tensor::PboMapping, Error> {
1031 let already = with_worker_mapped(|set| !set.insert(buffer_id)).unwrap_or(false);
1032 if already {
1033 return Err(Error::OpenGl(format!(
1034 "PBO buffer {buffer_id} is already mapped in this GL context \
1035 (a producer and a reconstructed cross-package consumer may \
1036 both hold a live tensor over it)"
1037 )));
1038 }
1039 let result = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
1040 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, buffer_id);
1041 let ptr = edgefirst_gl::gl::MapBufferRange(
1042 edgefirst_gl::gl::PIXEL_PACK_BUFFER,
1043 0,
1044 size as isize,
1045 edgefirst_gl::gl::MAP_READ_BIT | edgefirst_gl::gl::MAP_WRITE_BIT,
1046 );
1047 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
1048 if ptr.is_null() {
1049 Err(Error::OpenGl("glMapBufferRange returned null".to_string()))
1050 } else {
1051 Ok(edgefirst_tensor::PboMapping {
1052 ptr: ptr as *mut u8,
1053 size,
1054 })
1055 }
1056 }));
1057 if !matches!(result, Ok(Ok(_))) {
1058 with_worker_mapped(|set| set.remove(&buffer_id));
1061 }
1062 match result {
1063 Ok(result) => result,
1064 Err(panic) => std::panic::resume_unwind(panic),
1067 }
1068}
1069
1070fn pbo_unmap_on_gl_thread(buffer_id: u32) -> Result<(), Error> {
1072 with_worker_mapped(|set| set.remove(&buffer_id));
1075 unsafe {
1076 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, buffer_id);
1077 let ok = edgefirst_gl::gl::UnmapBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER);
1078 edgefirst_gl::gl::BindBuffer(edgefirst_gl::gl::PIXEL_PACK_BUFFER, 0);
1079 if ok == edgefirst_gl::gl::FALSE {
1080 Err(Error::OpenGl(
1081 "PBO data was corrupted during mapping".into(),
1082 ))
1083 } else {
1084 check_gl_error("PboUnmap", 0)
1085 }
1086 }
1087}
1088
1089fn handle_pbo_map(
1090 buffer_id: u32,
1091 size: usize,
1092 resp: tokio::sync::oneshot::Sender<Result<edgefirst_tensor::PboMapping, Error>>,
1093 poisoned: &mut bool,
1094) {
1095 let result =
1096 std::panic::catch_unwind(AssertUnwindSafe(|| pbo_map_on_gl_thread(buffer_id, size)));
1097 reply_caught(result, resp, poisoned, "PboMap");
1098}
1099
1100fn handle_pbo_unmap(
1101 buffer_id: u32,
1102 resp: tokio::sync::oneshot::Sender<Result<(), Error>>,
1103 poisoned: &mut bool,
1104) {
1105 let result = std::panic::catch_unwind(AssertUnwindSafe(|| pbo_unmap_on_gl_thread(buffer_id)));
1106 reply_caught(result, resp, poisoned, "PboUnmap");
1107}
1108
1109fn handle_pbo_delete(buffer_id: u32, poisoned: &mut bool) {
1110 with_worker_mapped(|set| set.remove(&buffer_id));
1117 if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe(|| unsafe {
1118 edgefirst_gl::gl::DeleteBuffers(1, &buffer_id);
1119 })) {
1120 *poisoned = true;
1121 log::error!(
1122 "GL thread panicked during PboDelete: {}",
1123 panic_message(e.as_ref()),
1124 );
1125 }
1126}
1127
1128impl GLProcessorThreaded {
1129 pub fn new(kind: Option<EglDisplayKind>) -> Result<Self, Error> {
1132 Self::with_cache_capacity(kind, None)
1133 }
1134
1135 pub fn with_cache_capacity(
1141 kind: Option<EglDisplayKind>,
1142 capacity: Option<usize>,
1143 ) -> Result<Self, Error> {
1144 let worker_id = NEXT_GL_WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1145 let (send, recv) = tokio::sync::mpsc::channel::<GLProcessorMessage>(1);
1146
1147 let (create_ctx_send, create_ctx_recv) = tokio::sync::oneshot::channel();
1148
1149 let owned_pbos = std::sync::Arc::<super::processor::OwnedPbos>::default();
1150 let worker_owned = owned_pbos.clone();
1151 let handle = std::thread::spawn(move || {
1152 run_gl_worker(
1153 worker_id,
1154 worker_owned,
1155 kind,
1156 capacity,
1157 recv,
1158 create_ctx_send,
1159 );
1160 });
1161
1162 let caps = match create_ctx_recv.blocking_recv() {
1163 Ok(Err(e)) => return Err(e),
1164 Err(_) => {
1165 return Err(Error::Internal(
1166 "GL converter error messaging closed without update".to_string(),
1167 ));
1168 }
1169 Ok(Ok(caps)) => caps,
1170 };
1171
1172 Ok(Self {
1173 handle: Some(handle),
1174 sender: Some(send),
1175 worker_id,
1176 owned_pbos,
1177 caps,
1178 })
1179 }
1180}
1181
1182impl ImageProcessorTrait for GLProcessorThreaded {
1183 fn convert(
1184 &mut self,
1185 src: &TensorDyn,
1186 dst: &mut TensorDyn,
1187 rotation: crate::Rotation,
1188 flip: Flip,
1189 crop: Crop,
1190 ) -> crate::Result<()> {
1191 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1192 self.sender
1193 .as_ref()
1194 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1195 .blocking_send(GLProcessorMessage::ImageConvert(
1196 SendablePtr {
1197 ptr: NonNull::from(src),
1198 len: 1,
1199 },
1200 SendablePtr {
1201 ptr: NonNull::from(dst),
1202 len: 1,
1203 },
1204 rotation,
1205 flip,
1206 crop,
1207 false, err_send,
1209 ))
1210 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1211 err_recv.blocking_recv().map_err(|_| {
1212 Error::Internal("GL converter error messaging closed without update".to_string())
1213 })?
1214 }
1215
1216 fn convert_deferred(
1217 &mut self,
1218 src: &TensorDyn,
1219 dst: &mut TensorDyn,
1220 rotation: crate::Rotation,
1221 flip: Flip,
1222 crop: Crop,
1223 ) -> crate::Result<()> {
1224 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1225 self.sender
1226 .as_ref()
1227 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1228 .blocking_send(GLProcessorMessage::ImageConvert(
1229 SendablePtr {
1230 ptr: NonNull::from(src),
1231 len: 1,
1232 },
1233 SendablePtr {
1234 ptr: NonNull::from(dst),
1235 len: 1,
1236 },
1237 rotation,
1238 flip,
1239 crop,
1240 true, err_send,
1242 ))
1243 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1244 err_recv.blocking_recv().map_err(|_| {
1245 Error::Internal("GL converter error messaging closed without update".to_string())
1246 })?
1247 }
1248
1249 fn flush(&mut self) -> crate::Result<()> {
1250 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1251 self.sender
1252 .as_ref()
1253 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1254 .blocking_send(GLProcessorMessage::Flush(err_send))
1255 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1256 err_recv.blocking_recv().map_err(|_| {
1257 Error::Internal("GL converter error messaging closed without update".to_string())
1258 })?
1259 }
1260
1261 fn draw_decoded_masks(
1262 &mut self,
1263 dst: &mut TensorDyn,
1264 detect: &[crate::DetectBox],
1265 segmentation: &[crate::Segmentation],
1266 overlay: crate::MaskOverlay<'_>,
1267 ) -> crate::Result<()> {
1268 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1269 self.sender
1270 .as_ref()
1271 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1272 .blocking_send(GLProcessorMessage::DrawDecodedMasks(
1273 SendablePtr {
1274 ptr: NonNull::from(dst),
1275 len: 1,
1276 },
1277 SendablePtr {
1278 ptr: NonNull::new(detect.as_ptr() as *mut DetectBox).unwrap(),
1279 len: detect.len(),
1280 },
1281 SendablePtr {
1282 ptr: NonNull::new(segmentation.as_ptr() as *mut Segmentation).unwrap(),
1283 len: segmentation.len(),
1284 },
1285 overlay.opacity,
1286 overlay.background.map(|bg| SendablePtr {
1287 ptr: NonNull::from(bg).cast::<TensorDyn>(),
1288 len: 1,
1289 }),
1290 overlay.letterbox,
1291 overlay.color_mode,
1292 err_send,
1293 ))
1294 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1295 err_recv.blocking_recv().map_err(|_| {
1296 Error::Internal("GL converter error messaging closed without update".to_string())
1297 })?
1298 }
1299
1300 fn draw_proto_masks(
1301 &mut self,
1302 dst: &mut TensorDyn,
1303 detect: &[DetectBox],
1304 proto_data: &ProtoData,
1305 overlay: crate::MaskOverlay<'_>,
1306 ) -> crate::Result<()> {
1307 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1308 self.sender
1309 .as_ref()
1310 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1311 .blocking_send(GLProcessorMessage::DrawProtoMasks(
1312 SendablePtr {
1313 ptr: NonNull::from(dst),
1314 len: 1,
1315 },
1316 SendablePtr {
1317 ptr: NonNull::new(detect.as_ptr() as *mut DetectBox).unwrap(),
1318 len: detect.len(),
1319 },
1320 SendablePtr {
1321 ptr: NonNull::from(proto_data).cast::<ProtoData>(),
1322 len: 1,
1323 },
1324 overlay.opacity,
1325 overlay.background.map(|bg| SendablePtr {
1326 ptr: NonNull::from(bg).cast::<TensorDyn>(),
1327 len: 1,
1328 }),
1329 overlay.letterbox,
1330 overlay.color_mode,
1331 err_send,
1332 ))
1333 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1334 err_recv.blocking_recv().map_err(|_| {
1335 Error::Internal("GL converter error messaging closed without update".to_string())
1336 })?
1337 }
1338
1339 fn set_class_colors(&mut self, colors: &[[u8; 4]]) -> Result<(), crate::Error> {
1340 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1341 self.sender
1342 .as_ref()
1343 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1344 .blocking_send(GLProcessorMessage::SetColors(colors.to_vec(), err_send))
1345 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1346 err_recv.blocking_recv().map_err(|_| {
1347 Error::Internal("GL converter error messaging closed without update".to_string())
1348 })?
1349 }
1350}
1351
1352impl GLProcessorThreaded {
1353 pub fn set_colorimetry_mode(&mut self, mode: crate::ColorimetryMode) -> Result<(), Error> {
1358 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1359 self.sender
1360 .as_ref()
1361 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1362 .blocking_send(GLProcessorMessage::SetColorimetryMode(mode, err_send))
1363 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1364 err_recv.blocking_recv().map_err(|_| {
1365 Error::Internal("GL converter error messaging closed without update".to_string())
1366 })?
1367 }
1368
1369 #[cfg(test)]
1375 pub(crate) fn set_fail_dst_import(&mut self, fail: bool) -> Result<(), Error> {
1376 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1377 self.sender
1378 .as_ref()
1379 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1380 .blocking_send(GLProcessorMessage::SetFailDstImport(fail, err_send))
1381 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1382 err_recv.blocking_recv().map_err(|_| {
1383 Error::Internal("GL converter error messaging closed without update".to_string())
1384 })?
1385 }
1386
1387 pub fn set_int8_interpolation_mode(
1389 &mut self,
1390 mode: Int8InterpolationMode,
1391 ) -> Result<(), crate::Error> {
1392 let (err_send, err_recv) = tokio::sync::oneshot::channel();
1393 self.sender
1394 .as_ref()
1395 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1396 .blocking_send(GLProcessorMessage::SetInt8Interpolation(mode, err_send))
1397 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1398 err_recv.blocking_recv().map_err(|_| {
1399 Error::Internal("GL converter error messaging closed without update".to_string())
1400 })?
1401 }
1402
1403 pub fn egl_cache_stats(&self) -> Result<super::cache::GlCacheStats, Error> {
1410 let (send, recv) = tokio::sync::oneshot::channel();
1411 self.sender
1412 .as_ref()
1413 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1414 .blocking_send(GLProcessorMessage::EglCacheStats(send))
1415 .map_err(|_| Error::Internal("GL converter thread exited".to_string()))?;
1416 recv.blocking_recv().map_err(|_| {
1417 Error::Internal("GL converter error messaging closed without update".to_string())
1418 })?
1419 }
1420
1421 pub fn convert_with_fence(
1432 &mut self,
1433 src: &TensorDyn,
1434 dst: &mut TensorDyn,
1435 rotation: crate::Rotation,
1436 flip: Flip,
1437 crop: Crop,
1438 ) -> Result<Option<super::CompletionFence>, Error> {
1439 use crate::ImageProcessorTrait as _;
1440 if !self.caps.native_fence_sync {
1441 self.convert(src, dst, rotation, flip, crop)?;
1444 return Ok(None);
1445 }
1446 let (send, recv) = tokio::sync::oneshot::channel();
1447 self.sender
1448 .as_ref()
1449 .ok_or_else(|| Error::Internal("GL processor is shutting down".to_string()))?
1450 .blocking_send(GLProcessorMessage::ImageConvertFenced(
1451 SendablePtr {
1452 ptr: NonNull::from(src),
1453 len: 1,
1454 },
1455 SendablePtr {
1456 ptr: NonNull::from(dst),
1457 len: 1,
1458 },
1459 rotation,
1460 flip,
1461 crop,
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(
1488 &self,
1489 width: usize,
1490 height: usize,
1491 format: edgefirst_tensor::PixelFormat,
1492 ) -> Result<edgefirst_tensor::Tensor<u8>, Error> {
1493 let sender = self
1494 .sender
1495 .as_ref()
1496 .ok_or(Error::OpenGl("GL processor is shutting down".to_string()))?;
1497
1498 let size = pbo_elem_count(width, height, format)
1499 .filter(|&s| s != 0)
1500 .ok_or_else(|| Error::OpenGl("Invalid image dimensions".to_string()))?;
1501
1502 let (tx, rx) = tokio::sync::oneshot::channel();
1504 sender
1505 .blocking_send(GLProcessorMessage::PboCreate(size, tx))
1506 .map_err(|_| Error::OpenGl("GL thread channel closed".to_string()))?;
1507 let buffer_id = rx
1508 .blocking_recv()
1509 .map_err(|_| Error::OpenGl("GL thread did not respond".to_string()))??;
1510
1511 let ops: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1512 sender: sender.downgrade(),
1513 worker_id: self.worker_id,
1514 });
1515 let owned_ops = ops.clone();
1516
1517 let shape = pbo_shape(width, height, format);
1518
1519 let pbo_tensor =
1520 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops)
1521 .map_err(|e| Error::OpenGl(format!("PBO tensor creation failed: {e:?}")))?;
1522 let mut tensor = edgefirst_tensor::Tensor::from_pbo(pbo_tensor)
1523 .map_err(|e| Error::OpenGl(format!("PBO tensor wrap failed: {e:?}")))?;
1524 tensor
1525 .set_format(format)
1526 .map_err(|e| Error::OpenGl(format!("Failed to set format on PBO tensor: {e:?}")))?;
1527 register_pbo_cuda(&mut tensor, buffer_id, size, sender);
1534 self.owned_pbos.insert(tensor.pbo_vtable_ptr(), &owned_ops);
1535 Ok(tensor)
1536 }
1537
1538 pub(crate) fn create_pbo_image_dtype(
1544 &self,
1545 width: usize,
1546 height: usize,
1547 format: edgefirst_tensor::PixelFormat,
1548 dtype: edgefirst_tensor::DType,
1549 ) -> Result<TensorDyn, Error> {
1550 let sender = self
1551 .sender
1552 .as_ref()
1553 .ok_or(Error::OpenGl("GL processor is shutting down".to_string()))?;
1554
1555 let elems = pbo_elem_count(width, height, format)
1556 .filter(|&e| e != 0)
1557 .ok_or_else(|| Error::OpenGl("Invalid image dimensions".to_string()))?;
1558
1559 let size = elems
1560 .checked_mul(dtype.size())
1561 .ok_or_else(|| Error::OpenGl("PBO size overflow".to_string()))?;
1562
1563 let (tx, rx) = tokio::sync::oneshot::channel();
1565 sender
1566 .blocking_send(GLProcessorMessage::PboCreate(size, tx))
1567 .map_err(|_| Error::OpenGl("GL thread channel closed".to_string()))?;
1568 let buffer_id = rx
1569 .blocking_recv()
1570 .map_err(|_| Error::OpenGl("GL thread did not respond".to_string()))??;
1571
1572 let ops: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1573 sender: sender.downgrade(),
1574 worker_id: self.worker_id,
1575 });
1576 let owned_ops = ops.clone();
1577
1578 let shape = pbo_shape(width, height, format);
1579
1580 let map_err = |e: edgefirst_tensor::Error| {
1581 Error::OpenGl(format!("PBO tensor creation failed: {e:?}"))
1582 };
1583 let set_err = |e: edgefirst_tensor::Error| {
1584 Error::OpenGl(format!("Failed to set format on PBO tensor: {e:?}"))
1585 };
1586
1587 let tensor = match dtype {
1588 edgefirst_tensor::DType::U8 => {
1589 let pbo =
1590 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops)
1591 .map_err(map_err)?;
1592 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo).map_err(map_err)?;
1593 t.set_format(format).map_err(set_err)?;
1594 register_pbo_cuda(&mut t, buffer_id, size, sender);
1595 TensorDyn::from(t)
1596 }
1597 edgefirst_tensor::DType::F16 => {
1598 let pbo = edgefirst_tensor::PboTensor::<edgefirst_tensor::f16>::from_pbo(
1599 buffer_id, size, &shape, None, ops,
1600 )
1601 .map_err(map_err)?;
1602 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo).map_err(map_err)?;
1603 t.set_format(format).map_err(set_err)?;
1604 register_pbo_cuda(&mut t, buffer_id, size, sender);
1605 TensorDyn::from(t)
1606 }
1607 edgefirst_tensor::DType::F32 => {
1608 let pbo = edgefirst_tensor::PboTensor::<f32>::from_pbo(
1609 buffer_id, size, &shape, None, ops,
1610 )
1611 .map_err(map_err)?;
1612 let mut t = edgefirst_tensor::Tensor::from_pbo(pbo).map_err(map_err)?;
1613 t.set_format(format).map_err(set_err)?;
1614 register_pbo_cuda(&mut t, buffer_id, size, sender);
1615 TensorDyn::from(t)
1616 }
1617 other => return Err(Error::OpenGl(format!("unsupported PBO dtype {other:?}"))),
1618 };
1619 self.owned_pbos.insert(tensor.pbo_vtable_ptr(), &owned_ops);
1620 Ok(tensor)
1621 }
1622
1623 pub(crate) fn transfer_backend(&self) -> TransferBackend {
1625 self.caps.transfer_backend
1626 }
1627
1628 pub(crate) fn supported_render_dtypes(&self) -> crate::RenderDtypeSupport {
1635 self.caps.render_dtypes
1636 }
1637}
1638
1639impl Drop for GLProcessorThreaded {
1640 fn drop(&mut self) {
1641 drop(self.sender.take());
1647 let _ = self.handle.take().and_then(|h| h.join().ok());
1648 }
1649}
1650
1651#[cfg(test)]
1655#[cfg_attr(coverage_nightly, coverage(off))]
1656mod tests {
1657 use super::{pbo_elem_count, pbo_shape, GLProcessorThreaded, GlPboOps};
1658 use edgefirst_tensor::{PixelFormat, TensorTrait};
1659
1660 #[test]
1661 fn elem_count_per_format() {
1662 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Rgba), Some(8 * 4 * 4));
1664 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Rgb), Some(8 * 4 * 3));
1666 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Nv12), Some(8 * 4 * 3 / 2));
1668 assert_eq!(pbo_elem_count(8, 4, PixelFormat::Nv16), Some(8 * 4 * 2));
1670 }
1671
1672 #[test]
1673 fn elem_count_overflow_is_none() {
1674 assert_eq!(pbo_elem_count(usize::MAX, 2, PixelFormat::Rgba), None);
1676 assert_eq!(pbo_elem_count(usize::MAX, 1, PixelFormat::Rgb), None);
1678 }
1679
1680 #[test]
1681 fn shape_per_format() {
1682 assert_eq!(pbo_shape(8, 4, PixelFormat::PlanarRgb), vec![3, 4, 8]);
1684 assert_eq!(pbo_shape(8, 4, PixelFormat::Nv12), vec![4 * 3 / 2, 8]);
1686 assert_eq!(pbo_shape(8, 4, PixelFormat::Nv16), vec![4 * 2, 8]);
1688 assert_eq!(pbo_shape(8, 4, PixelFormat::Rgba), vec![4, 8, 4]);
1690 }
1691
1692 #[test]
1710 #[cfg(any(target_os = "linux", target_os = "windows"))] fn concurrent_maps_from_two_independent_handles_on_one_buffer_do_not_corrupt_gl_state() {
1712 let gl = match GLProcessorThreaded::new(None) {
1713 Ok(gl) => gl,
1714 Err(e) => {
1715 crate::test_support::report_skip(&format!(
1716 "concurrent_maps_from_two_independent_handles... - \
1717 OpenGL not available: {e:?}"
1718 ));
1719 return;
1720 }
1721 };
1722 let tensor_a = match gl.create_pbo_image(64, 64, PixelFormat::Rgba) {
1723 Ok(t) => t,
1724 Err(e) => {
1725 crate::test_support::report_skip(&format!(
1726 "concurrent_maps_from_two_independent_handles... - \
1727 PBO not supported: {e:?}"
1728 ));
1729 return;
1730 }
1731 };
1732 let buffer_id = tensor_a.pbo_id().expect("just created a PBO tensor");
1733 let size = tensor_a.capacity_bytes();
1734 let shape = tensor_a.shape().to_vec();
1735
1736 let ops_b: std::sync::Arc<dyn edgefirst_tensor::PboOps> = std::sync::Arc::new(GlPboOps {
1740 sender: gl.sender.as_ref().unwrap().downgrade(),
1741 worker_id: gl.worker_id,
1742 });
1743 let tensor_b =
1744 edgefirst_tensor::PboTensor::<u8>::from_pbo(buffer_id, size, &shape, None, ops_b)
1745 .expect("from_pbo against a real, already-created buffer_id must succeed");
1746
1747 let map_a = tensor_a.map().expect("first map must succeed");
1749
1750 let map_b_result = tensor_b.map();
1754 let Err(map_b_err) = map_b_result else {
1762 panic!(
1763 "a second, independent handle mapping the SAME real buffer while the \
1764 first mapping is still open must be refused, not silently succeed"
1765 );
1766 };
1767 let msg = format!("{map_b_err:?}");
1768 assert!(
1769 msg.contains("already mapped"),
1770 "must be refused specifically by the GL-thread-level reservation check \
1771 (mapped_pbo_buffers), not some other error path: {msg}"
1772 );
1773
1774 drop(map_a);
1775
1776 let map_b_after = tensor_b.map();
1780 assert!(
1781 map_b_after.is_ok(),
1782 "after the first handle's mapping releases, the second handle must be able to \
1783 map the same buffer: {map_b_after:?}"
1784 );
1785 }
1786}