edgefirst_image/gl/mod.rs
1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4#![cfg(any(target_os = "linux", target_os = "macos"))]
5#![cfg(feature = "opengl")]
6// Several types defined at the `gl` module root (EglDisplayKind,
7// TransferBackend, RegionOfInterest, etc.) are consumed only by the
8// Linux-only inner modules (`context`, `processor`, ...). The macOS
9// path uses its own `MacosGlProcessor` + `iosurface_import` modules and
10// does not touch every shared type, so some appear unused on macOS.
11// Rather than fragmenting the type definitions per platform, suppress
12// the dead-code lint on non-Linux targets.
13#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
14
15// Module layout:
16// - `platform/` — cross-platform display/EGL-loader seam (both OSes)
17// - Linux-only: `context`, `processor`, `threaded`, `dma_import`,
18// `cache`, `resources`, `shaders`, `tests`
19// - macOS-only: `iosurface_import`, `macos_processor`
20// The macOS processor is parallel to (not a refactor of) the Linux
21// threaded processor — see `crates/image/ARCHITECTURE.md` for the
22// rationale and the planned convergence story.
23
24macro_rules! function {
25 () => {{
26 fn f() {}
27 fn type_name_of<T>(_: T) -> &'static str {
28 std::any::type_name::<T>()
29 }
30 let name = type_name_of(f);
31
32 // Find and cut the rest of the path
33 match &name[..name.len() - 3].rfind(':') {
34 Some(pos) => &name[pos + 1..name.len() - 3],
35 None => &name[..name.len() - 3],
36 }
37 }};
38}
39
40mod cache;
41#[cfg(target_os = "linux")]
42mod context;
43#[cfg(target_os = "linux")]
44mod dma_import;
45// Cfg-agnostic: the float render-path classifier is the single source of
46// truth for the "(PixelFormat, DType, TensorMemory) → float path" decision and
47// is compiled on every platform so both the Linux and macOS backends share one
48// definition (see `crates/image/ARCHITECTURE.md`, review item #4).
49// Portable renderer helpers (crop uniforms, …) shared by both platform
50// backends. No gbm/IOSurface types; compiled on both.
51mod core;
52mod float_dispatch;
53// Pure decision table for the proto-segmentation render path (upload
54// strategy × program × count uniform). No GL types; host-tested
55// exhaustively. See `proto_dispatch.rs`.
56mod proto_dispatch;
57// Portable GL render lowering (y-flip viewport, source UV, batch chunk planner).
58// No platform types; compiled on both platforms, consumed by the converged
59// tile/batch renderer. See `render.rs`.
60mod render;
61// PixelFormat -> DRM FourCC mapping via the portable `drm_fourcc` crate (NOT
62// `gbm`). DRM FourCC is a Linux/DMA-BUF concept, so this lives with the other
63// Linux graphics modules; the point is that it carries no `gbm` coupling, so
64// `shaders.rs` and the format code no longer pull in `gbm`.
65#[cfg(target_os = "linux")]
66mod fourcc;
67#[cfg(target_os = "macos")]
68mod iosurface_import;
69mod platform;
70mod processor;
71mod resources;
72mod shaders;
73mod shaders_common;
74// Engine GL tests, in three tiers (see crates/image/TESTING.md):
75// portable — run on Linux AND macOS/ANGLE
76// cfg(target_os = "linux") — display probing, PBO/CUDA paths
77// cfg(all(linux, dma_test_formats)) — DMA-BUF import/pool specifics
78// Per-item cfg gates inside the module select the tier; the mount
79// itself is unconditional so the macOS lane runs the portable tier.
80mod tests;
81mod threaded;
82
83#[cfg(target_os = "linux")]
84pub use context::probe_egl_displays;
85// These are accessed by sibling sub-modules via `super::context::` directly.
86// No re-export needed at the mod.rs level.
87pub use cache::{CacheStats, GlCacheStats};
88pub use threaded::GLProcessorThreaded;
89
90/// Dynamically-loaded EGL 1.4 instance. The lifetime parameter is
91/// `'static` because the underlying `libloading::Library` is intentionally
92/// leaked at first load (see `EGL_LIB` in `context.rs` and the equivalent
93/// on macOS — drivers may retain internal state past explicit cleanup, so
94/// dlclose can SIGBUS on process exit).
95///
96/// Defined here at the `gl` module root so the `platform/` trait and both
97/// platform implementations can name it without dragging in a cross-cfg
98/// re-export. The Linux `context.rs` and the macOS `platform/macos.rs`
99/// both use this same alias.
100pub(super) type Egl =
101 khronos_egl::Instance<khronos_egl::Dynamic<&'static libloading::Library, khronos_egl::EGL1_4>>;
102
103/// Identifies the type of EGL display used for headless OpenGL ES rendering.
104///
105/// The HAL creates a surfaceless GLES 3.0 context
106/// (`EGL_KHR_surfaceless_context` + `EGL_KHR_no_config_context`) and
107/// renders exclusively through FBOs backed by EGLImages imported from
108/// DMA-buf file descriptors. No window or PBuffer surface is created.
109///
110/// Displays are probed in priority order: PlatformDevice first (zero
111/// external dependencies), then GBM, then Default. Use
112/// [`probe_egl_displays`] to discover which are available and
113/// [`ImageProcessorConfig::egl_display`](crate::ImageProcessorConfig::egl_display)
114/// to override the auto-detection.
115///
116/// # Display Types
117///
118/// - **`PlatformDevice`** — Uses `EGL_EXT_device_enumeration` to query
119/// available EGL devices via `eglQueryDevicesEXT`, then selects the first
120/// device with `eglGetPlatformDisplay(EGL_EXT_platform_device, ...)`.
121/// Headless and compositor-free with zero external library dependencies.
122/// Works on NVIDIA GPUs and newer Vivante drivers.
123///
124/// - **`Gbm`** — Opens a DRM render node (e.g. `/dev/dri/renderD128`) and
125/// creates a GBM (Generic Buffer Manager) device, then calls
126/// `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm_device)`. Requires
127/// `libgbm` and a DRM render node. Needed on ARM Mali (i.MX95) and older
128/// Vivante drivers that do not expose `EGL_EXT_platform_device`.
129///
130/// - **`Default`** — Calls `eglGetDisplay(EGL_DEFAULT_DISPLAY)`, letting the
131/// EGL implementation choose the display. On Wayland systems this connects
132/// to the compositor; on X11 it connects to the X server. May block on
133/// headless systems where a compositor is expected but not running.
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135pub enum EglDisplayKind {
136 Gbm,
137 PlatformDevice,
138 Default,
139}
140
141impl std::fmt::Display for EglDisplayKind {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 EglDisplayKind::Gbm => write!(f, "GBM"),
145 EglDisplayKind::PlatformDevice => write!(f, "PlatformDevice"),
146 EglDisplayKind::Default => write!(f, "Default"),
147 }
148 }
149}
150
151/// A validated, available EGL display discovered by [`probe_egl_displays`].
152#[derive(Debug, Clone)]
153pub struct EglDisplayInfo {
154 /// The type of EGL display.
155 pub kind: EglDisplayKind,
156 /// Human-readable description for logging/diagnostics
157 /// (e.g. "GBM via /dev/dri/renderD128").
158 pub description: String,
159}
160
161/// Tracks which data-transfer method is active for moving pixels
162/// between CPU memory and GPU textures/framebuffers.
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub(crate) enum TransferBackend {
165 /// Zero-copy via EGLImage imported from DMA-buf file descriptors.
166 /// Available on i.MX8 (Vivante), i.MX95 (Mali), Jetson, and any
167 /// platform where `EGL_EXT_image_dma_buf_import` is present AND
168 /// the GPU can actually render through DMA-buf-backed textures.
169 DmaBuf,
170
171 /// Zero-copy via `EGL_ANGLE_iosurface_client_buffer` (macOS).
172 /// Available when ANGLE's Metal backend is loaded and the EGL
173 /// extension is advertised. The IOSurface is wrapped as an EGL
174 /// pbuffer and bound to a 2D texture via `eglBindTexImage`.
175 #[cfg(target_os = "macos")]
176 IOSurface,
177
178 /// GPU buffer via Pixel Buffer Object. Used when DMA-buf is unavailable
179 /// but OpenGL is present. Data stays in GPU-accessible memory.
180 Pbo,
181
182 /// Synchronous `glTexSubImage2D` upload + `glReadPixels` readback.
183 /// Used when DMA-buf is unavailable or when the DMA-buf verification
184 /// probe fails (e.g. NVIDIA discrete GPUs where EGLImage creation
185 /// succeeds but rendered data is all zeros).
186 Sync,
187}
188
189impl TransferBackend {
190 /// Returns `true` if DMA-buf zero-copy is available.
191 pub(crate) fn is_dma(self) -> bool {
192 self == TransferBackend::DmaBuf
193 }
194
195 /// Returns `true` if the platform can import `TensorMemory::Dma`
196 /// tensors zero-copy: DMA-BUF EGLImages on Linux, IOSurface pbuffers
197 /// on macOS. Path-selection sites use this; probes that are
198 /// specifically about DMA-BUF semantics (e.g. the render-roundtrip
199 /// verification) keep `is_dma`.
200 pub(crate) fn is_zero_copy(self) -> bool {
201 #[cfg(target_os = "macos")]
202 if self == TransferBackend::IOSurface {
203 return true;
204 }
205 self == TransferBackend::DmaBuf
206 }
207}
208
209/// Interpolation mode for int8 proto textures (GL_R8I cannot use GL_LINEAR).
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum Int8InterpolationMode {
212 /// texelFetch at nearest texel — simplest, fastest GPU execution.
213 Nearest,
214 /// texelFetch × 4 neighbors with shader-computed bilinear weights (default).
215 Bilinear,
216 /// Two-pass: dequant int8→f16 FBO, then existing f16 shader with GL_LINEAR.
217 TwoPass,
218}
219
220/// A rectangular region of interest expressed as normalised [0, 1] coordinates.
221#[derive(Debug, Clone, Copy)]
222pub(super) struct RegionOfInterest {
223 pub(super) left: f32,
224 pub(super) top: f32,
225 pub(super) right: f32,
226 pub(super) bottom: f32,
227}
228
229impl RegionOfInterest {
230 /// Build a source ROI from a pixel-space crop rectangle with a half-texel
231 /// inset. The inset ensures that `GL_LINEAR` filtering never samples
232 /// outside the crop boundary — at the extreme texture coordinates the
233 /// bilinear kernel is centred on the boundary texel and cannot reach
234 /// adjacent padding pixels.
235 ///
236 /// The result is clamped to [0, 1] so an out-of-bounds crop rectangle
237 /// cannot produce invalid texture coordinates.
238 ///
239 /// `crop`: pixel-space rectangle (left, top, width, height).
240 /// `tex_w`, `tex_h`: full texture dimensions in pixels.
241 pub(super) fn from_crop_clamped(crop: &crate::Rect, tex_w: usize, tex_h: usize) -> Self {
242 let half_x = 0.5 / tex_w as f32;
243 let half_y = 0.5 / tex_h as f32;
244 RegionOfInterest {
245 left: (crop.left as f32 / tex_w as f32 + half_x).clamp(0.0, 1.0),
246 top: ((crop.top + crop.height) as f32 / tex_h as f32 - half_y).clamp(0.0, 1.0),
247 right: ((crop.left + crop.width) as f32 / tex_w as f32 - half_x).clamp(0.0, 1.0),
248 bottom: (crop.top as f32 / tex_h as f32 + half_y).clamp(0.0, 1.0),
249 }
250 }
251}