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