Skip to main content

azul_core/
gl.rs

1//! OpenGL context wrappers, texture cache management, shader compilation,
2//! vertex buffer abstractions, and FFI-safe GL type aliases for the C/Python API.
3
4#![allow(unused_variables)]
5use alloc::{
6    boxed::Box,
7    rc::Rc,
8    string::{String, ToString},
9    vec::Vec,
10};
11use core::{
12    ffi, fmt,
13    hash::{Hash, Hasher},
14    mem::ManuallyDrop,
15    sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
16};
17
18use azul_css::{
19    props::{
20        basic::{ColorF, ColorU},
21        style::StyleTransformVec,
22    },
23    AzString, OptionI32, OptionU32, OptionUsize, StringVec, U8Vec,
24};
25pub use gl_context_loader::{
26    ctypes::*, gl, GLeglImageOES, GLsync, GLvoid, GenericGlContext, GlType as GlContextGlType,
27};
28
29pub use crate::glconst::*;
30use crate::{
31    geom::PhysicalSizeU32,
32    hit_test::DocumentId,
33    resources::{
34        Brush, Epoch, ExternalImageId, ImageDescriptor, ImageDescriptorFlags, RawImage,
35        RawImageData, RawImageFormat,
36    },
37    svg::{TessellatedGPUSvgNode, TessellatedSvgNode},
38    window::RendererType,
39    OrderedMap,
40};
41
42pub type GLuint = u32;
43pub type GLint = i32;
44pub type GLint64 = i64;
45pub type GLuint64 = u64;
46pub type GLenum = u32;
47pub type GLintptr = isize;
48pub type GLboolean = u8;
49pub type GLsizeiptr = isize;
50pub type GLbitfield = u32;
51pub type GLsizei = i32;
52pub type GLclampf = f32;
53pub type GLfloat = f32;
54
55pub const GL_RESTART_INDEX: u32 = core::u32::MAX;
56
57/// Passing *const `c_void` is not easily possible when generating APIs,
58/// so this wrapper struct is for easier API generation
59#[repr(C)]
60#[derive(Debug)]
61pub struct GlVoidPtrConst {
62    pub ptr: *const GLvoid,
63    pub run_destructor: bool,
64}
65
66impl Clone for GlVoidPtrConst {
67    fn clone(&self) -> Self {
68        Self {
69            ptr: self.ptr,
70            run_destructor: true,
71        }
72    }
73}
74
75impl Drop for GlVoidPtrConst {
76    fn drop(&mut self) {
77        self.run_destructor = false;
78    }
79}
80
81/// Struct returned from the C API
82///
83/// Because of Python, every object has to be clone-able,
84/// so yes there may exist more than one mutable reference
85#[repr(C)]
86#[derive(Debug)]
87pub struct GlVoidPtrMut {
88    pub ptr: *mut GLvoid,
89}
90
91impl Clone for GlVoidPtrMut {
92    fn clone(&self) -> Self {
93        Self { ptr: self.ptr }
94    }
95}
96
97/// FFI-safe wrapper for `&str`.
98#[repr(C)]
99pub struct Refstr {
100    pub ptr: *const u8,
101    pub len: usize,
102}
103
104impl Clone for Refstr {
105    fn clone(&self) -> Self {
106        Self {
107            ptr: self.ptr,
108            len: self.len,
109        }
110    }
111}
112
113impl fmt::Debug for Refstr {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        self.as_str().fmt(f)
116    }
117}
118
119impl Refstr {
120    #[must_use] pub const fn as_str(&self) -> &str {
121        // AUDIT: `from_raw_parts`/`from_utf8_unchecked` are UB on a null ptr
122        // (even with len==0). FFI callers can hand us a null/empty Refstr, so
123        // return an empty `&str` instead of forming a slice over null.
124        if self.ptr.is_null() || self.len == 0 {
125            return "";
126        }
127        unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.ptr, self.len)) }
128    }
129}
130
131impl From<&str> for Refstr {
132    fn from(s: &str) -> Self {
133        Self {
134            ptr: s.as_ptr(),
135            len: s.len(),
136        }
137    }
138}
139
140/// FFI-safe wrapper for `&[&str]`.
141#[repr(C)]
142pub struct RefstrVecRef {
143    pub ptr: *const Refstr,
144    pub len: usize,
145}
146
147impl Clone for RefstrVecRef {
148    fn clone(&self) -> Self {
149        Self {
150            ptr: self.ptr,
151            len: self.len,
152        }
153    }
154}
155
156impl fmt::Debug for RefstrVecRef {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        self.as_slice().fmt(f)
159    }
160}
161
162impl RefstrVecRef {
163    #[must_use] pub const fn as_slice(&self) -> &[Refstr] {
164        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
165        if self.ptr.is_null() || self.len == 0 {
166            return &[];
167        }
168        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
169    }
170}
171
172impl From<&[Refstr]> for RefstrVecRef {
173    fn from(s: &[Refstr]) -> Self {
174        Self {
175            ptr: s.as_ptr(),
176            len: s.len(),
177        }
178    }
179}
180
181/// FFI-safe wrapper for `&mut [GLint64]`.
182#[repr(C)]
183pub struct GLint64VecRefMut {
184    pub ptr: *mut i64,
185    pub len: usize,
186}
187
188impl Clone for GLint64VecRefMut {
189    fn clone(&self) -> Self {
190        Self {
191            ptr: self.ptr,
192            len: self.len,
193        }
194    }
195}
196
197impl fmt::Debug for GLint64VecRefMut {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        self.as_slice().fmt(f)
200    }
201}
202
203impl From<&mut [GLint64]> for GLint64VecRefMut {
204    fn from(s: &mut [GLint64]) -> Self {
205        Self {
206            ptr: s.as_mut_ptr(),
207            len: s.len(),
208        }
209    }
210}
211
212impl GLint64VecRefMut {
213    #[must_use] pub const fn as_slice(&self) -> &[GLint64] {
214        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
215        if self.ptr.is_null() || self.len == 0 {
216            return &[];
217        }
218        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
219    }
220    const fn as_mut_slice(&mut self) -> &mut [GLint64] {
221        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
222        if self.ptr.is_null() || self.len == 0 {
223            return &mut [];
224        }
225        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
226    }
227}
228
229/// FFI-safe wrapper for `&mut [GLfloat]`.
230#[repr(C)]
231pub struct GLfloatVecRefMut {
232    pub ptr: *mut f32,
233    pub len: usize,
234}
235
236impl Clone for GLfloatVecRefMut {
237    fn clone(&self) -> Self {
238        Self {
239            ptr: self.ptr,
240            len: self.len,
241        }
242    }
243}
244
245impl fmt::Debug for GLfloatVecRefMut {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        self.as_slice().fmt(f)
248    }
249}
250
251impl From<&mut [GLfloat]> for GLfloatVecRefMut {
252    fn from(s: &mut [GLfloat]) -> Self {
253        Self {
254            ptr: s.as_mut_ptr(),
255            len: s.len(),
256        }
257    }
258}
259
260impl GLfloatVecRefMut {
261    #[must_use] pub const fn as_slice(&self) -> &[GLfloat] {
262        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
263        if self.ptr.is_null() || self.len == 0 {
264            return &[];
265        }
266        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
267    }
268    const fn as_mut_slice(&mut self) -> &mut [GLfloat] {
269        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
270        if self.ptr.is_null() || self.len == 0 {
271            return &mut [];
272        }
273        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
274    }
275}
276
277/// FFI-safe wrapper for `&mut [GLint]`.
278#[repr(C)]
279pub struct GLintVecRefMut {
280    pub ptr: *mut i32,
281    pub len: usize,
282}
283
284impl Clone for GLintVecRefMut {
285    fn clone(&self) -> Self {
286        Self {
287            ptr: self.ptr,
288            len: self.len,
289        }
290    }
291}
292
293impl fmt::Debug for GLintVecRefMut {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        self.as_slice().fmt(f)
296    }
297}
298
299impl From<&mut [GLint]> for GLintVecRefMut {
300    fn from(s: &mut [GLint]) -> Self {
301        Self {
302            ptr: s.as_mut_ptr(),
303            len: s.len(),
304        }
305    }
306}
307
308impl GLintVecRefMut {
309    #[must_use] pub const fn as_slice(&self) -> &[GLint] {
310        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
311        if self.ptr.is_null() || self.len == 0 {
312            return &[];
313        }
314        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
315    }
316    const fn as_mut_slice(&mut self) -> &mut [GLint] {
317        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
318        if self.ptr.is_null() || self.len == 0 {
319            return &mut [];
320        }
321        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
322    }
323}
324
325/// FFI-safe wrapper for `&[GLuint]`.
326#[repr(C)]
327pub struct GLuintVecRef {
328    pub ptr: *const u32,
329    pub len: usize,
330}
331
332impl Clone for GLuintVecRef {
333    fn clone(&self) -> Self {
334        Self {
335            ptr: self.ptr,
336            len: self.len,
337        }
338    }
339}
340
341impl fmt::Debug for GLuintVecRef {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        self.as_slice().fmt(f)
344    }
345}
346
347impl From<&[GLuint]> for GLuintVecRef {
348    fn from(s: &[GLuint]) -> Self {
349        Self {
350            ptr: s.as_ptr(),
351            len: s.len(),
352        }
353    }
354}
355
356impl GLuintVecRef {
357    #[must_use] pub const fn as_slice(&self) -> &[GLuint] {
358        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
359        if self.ptr.is_null() || self.len == 0 {
360            return &[];
361        }
362        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
363    }
364}
365
366/// FFI-safe wrapper for `&[GLenum]`.
367#[repr(C)]
368pub struct GLenumVecRef {
369    pub ptr: *const u32,
370    pub len: usize,
371}
372
373impl Clone for GLenumVecRef {
374    fn clone(&self) -> Self {
375        Self {
376            ptr: self.ptr,
377            len: self.len,
378        }
379    }
380}
381
382impl fmt::Debug for GLenumVecRef {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        self.as_slice().fmt(f)
385    }
386}
387
388impl From<&[GLenum]> for GLenumVecRef {
389    fn from(s: &[GLenum]) -> Self {
390        Self {
391            ptr: s.as_ptr(),
392            len: s.len(),
393        }
394    }
395}
396
397impl GLenumVecRef {
398    #[must_use] pub const fn as_slice(&self) -> &[GLenum] {
399        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
400        if self.ptr.is_null() || self.len == 0 {
401            return &[];
402        }
403        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
404    }
405}
406
407/// FFI-safe wrapper for `&[u8]`.
408#[repr(C)]
409pub struct U8VecRef {
410    pub ptr: *const u8,
411    pub len: usize,
412}
413
414impl Clone for U8VecRef {
415    fn clone(&self) -> Self {
416        Self {
417            ptr: self.ptr,
418            len: self.len,
419        }
420    }
421}
422
423impl From<&[u8]> for U8VecRef {
424    fn from(s: &[u8]) -> Self {
425        Self {
426            ptr: s.as_ptr(),
427            len: s.len(),
428        }
429    }
430}
431
432impl U8VecRef {
433    #[must_use] pub const fn as_slice(&self) -> &[u8] {
434        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
435        if self.ptr.is_null() || self.len == 0 {
436            return &[];
437        }
438        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
439    }
440}
441
442impl fmt::Debug for U8VecRef {
443    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444        self.as_slice().fmt(f)
445    }
446}
447
448impl PartialOrd for U8VecRef {
449    fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
450        self.as_slice().partial_cmp(rhs.as_slice())
451    }
452}
453
454impl Ord for U8VecRef {
455    fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
456        self.as_slice().cmp(rhs.as_slice())
457    }
458}
459
460impl PartialEq for U8VecRef {
461    fn eq(&self, rhs: &Self) -> bool {
462        self.as_slice().eq(rhs.as_slice())
463    }
464}
465
466impl Eq for U8VecRef {}
467
468impl Hash for U8VecRef {
469    fn hash<H>(&self, state: &mut H)
470    where
471        H: Hasher,
472    {
473        self.as_slice().hash(state);
474    }
475}
476
477/// FFI-safe wrapper for `&[f32]`.
478#[repr(C)]
479pub struct F32VecRef {
480    pub ptr: *const f32,
481    pub len: usize,
482}
483
484impl Clone for F32VecRef {
485    fn clone(&self) -> Self {
486        Self {
487            ptr: self.ptr,
488            len: self.len,
489        }
490    }
491}
492
493impl fmt::Debug for F32VecRef {
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        self.as_slice().fmt(f)
496    }
497}
498
499impl From<&[f32]> for F32VecRef {
500    fn from(s: &[f32]) -> Self {
501        Self {
502            ptr: s.as_ptr(),
503            len: s.len(),
504        }
505    }
506}
507
508impl F32VecRef {
509    #[must_use] pub const fn as_slice(&self) -> &[f32] {
510        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
511        if self.ptr.is_null() || self.len == 0 {
512            return &[];
513        }
514        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
515    }
516}
517
518/// FFI-safe wrapper for `&[i32]`.
519#[repr(C)]
520pub struct I32VecRef {
521    pub ptr: *const i32,
522    pub len: usize,
523}
524
525impl Clone for I32VecRef {
526    fn clone(&self) -> Self {
527        Self {
528            ptr: self.ptr,
529            len: self.len,
530        }
531    }
532}
533
534impl fmt::Debug for I32VecRef {
535    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536        self.as_slice().fmt(f)
537    }
538}
539
540impl From<&[i32]> for I32VecRef {
541    fn from(s: &[i32]) -> Self {
542        Self {
543            ptr: s.as_ptr(),
544            len: s.len(),
545        }
546    }
547}
548
549impl I32VecRef {
550    #[must_use] pub const fn as_slice(&self) -> &[i32] {
551        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
552        if self.ptr.is_null() || self.len == 0 {
553            return &[];
554        }
555        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
556    }
557}
558
559/// FFI-safe wrapper for `&mut [GLboolean]` (i.e. `&mut [u8]`).
560#[repr(C)]
561pub struct GLbooleanVecRefMut {
562    pub ptr: *mut u8,
563    pub len: usize,
564}
565
566impl Clone for GLbooleanVecRefMut {
567    fn clone(&self) -> Self {
568        Self {
569            ptr: self.ptr,
570            len: self.len,
571        }
572    }
573}
574
575impl fmt::Debug for GLbooleanVecRefMut {
576    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577        self.as_slice().fmt(f)
578    }
579}
580
581impl From<&mut [GLboolean]> for GLbooleanVecRefMut {
582    fn from(s: &mut [GLboolean]) -> Self {
583        Self {
584            ptr: s.as_mut_ptr(),
585            len: s.len(),
586        }
587    }
588}
589
590impl GLbooleanVecRefMut {
591    #[must_use] pub const fn as_slice(&self) -> &[GLboolean] {
592        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
593        if self.ptr.is_null() || self.len == 0 {
594            return &[];
595        }
596        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
597    }
598    const fn as_mut_slice(&mut self) -> &mut [GLboolean] {
599        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
600        if self.ptr.is_null() || self.len == 0 {
601            return &mut [];
602        }
603        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
604    }
605}
606
607/// FFI-safe wrapper for `&mut [u8]`.
608#[repr(C)]
609pub struct U8VecRefMut {
610    pub ptr: *mut u8,
611    pub len: usize,
612}
613
614impl Clone for U8VecRefMut {
615    fn clone(&self) -> Self {
616        Self {
617            ptr: self.ptr,
618            len: self.len,
619        }
620    }
621}
622
623impl fmt::Debug for U8VecRefMut {
624    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625        self.as_slice().fmt(f)
626    }
627}
628
629impl From<&mut [u8]> for U8VecRefMut {
630    fn from(s: &mut [u8]) -> Self {
631        Self {
632            ptr: s.as_mut_ptr(),
633            len: s.len(),
634        }
635    }
636}
637
638impl U8VecRefMut {
639    #[must_use] pub const fn as_slice(&self) -> &[u8] {
640        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
641        if self.ptr.is_null() || self.len == 0 {
642            return &[];
643        }
644        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
645    }
646    const fn as_mut_slice(&mut self) -> &mut [u8] {
647        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
648        if self.ptr.is_null() || self.len == 0 {
649            return &mut [];
650        }
651        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
652    }
653}
654
655impl_option!(
656    U8VecRef,
657    OptionU8VecRef,
658    copy = false,
659    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
660);
661
662#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
663#[repr(C)]
664pub struct DebugMessage {
665    pub message: AzString,
666    pub source: GLenum,
667    pub ty: GLenum,
668    pub id: GLenum,
669    pub severity: GLenum,
670}
671
672impl_option!(
673    DebugMessage,
674    OptionDebugMessage,
675    copy = false,
676    [Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
677);
678
679impl_vec!(DebugMessage, DebugMessageVec, DebugMessageVecDestructor, DebugMessageVecDestructorType, DebugMessageVecSlice, OptionDebugMessage);
680impl_vec_debug!(DebugMessage, DebugMessageVec);
681impl_vec_partialord!(DebugMessage, DebugMessageVec);
682impl_vec_ord!(DebugMessage, DebugMessageVec);
683impl_vec_clone!(DebugMessage, DebugMessageVec, DebugMessageVecDestructor);
684impl_vec_partialeq!(DebugMessage, DebugMessageVec);
685impl_vec_eq!(DebugMessage, DebugMessageVec);
686impl_vec_hash!(DebugMessage, DebugMessageVec);
687
688impl_vec!(GLint, GLintVec, GLintVecDestructor, GLintVecDestructorType, GLintVecSlice, OptionI32);
689impl_vec_debug!(GLint, GLintVec);
690impl_vec_partialord!(GLint, GLintVec);
691impl_vec_ord!(GLint, GLintVec);
692impl_vec_clone!(GLint, GLintVec, GLintVecDestructor);
693impl_vec_partialeq!(GLint, GLintVec);
694impl_vec_eq!(GLint, GLintVec);
695impl_vec_hash!(GLint, GLintVec);
696
697impl_vec!(GLuint, GLuintVec, GLuintVecDestructor, GLuintVecDestructorType, GLuintVecSlice, OptionU32);
698impl_vec_debug!(GLuint, GLuintVec);
699impl_vec_partialord!(GLuint, GLuintVec);
700impl_vec_ord!(GLuint, GLuintVec);
701impl_vec_clone!(GLuint, GLuintVec, GLuintVecDestructor);
702impl_vec_partialeq!(GLuint, GLuintVec);
703impl_vec_eq!(GLuint, GLuintVec);
704impl_vec_hash!(GLuint, GLuintVec);
705
706#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
707#[repr(C)]
708pub enum GlType {
709    Gl,
710    Gles,
711}
712
713impl From<GlContextGlType> for GlType {
714    fn from(a: GlContextGlType) -> Self {
715        match a {
716            GlContextGlType::Gl => Self::Gl,
717            GlContextGlType::GlEs => Self::Gles,
718        }
719    }
720}
721
722// (U8Vec, u32)
723#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
724#[repr(C)]
725// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
726#[allow(clippy::pub_underscore_fields)]
727pub struct GetProgramBinaryReturn {
728    pub _0: U8Vec,
729    pub _1: u32,
730}
731
732// (i32, u32, AzString)
733#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
734#[repr(C)]
735// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
736#[allow(clippy::pub_underscore_fields)]
737pub struct GetActiveAttribReturn {
738    pub _0: i32,
739    pub _1: u32,
740    pub _2: AzString,
741}
742
743// (i32, u32, AzString)
744#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
745#[repr(C)]
746// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
747#[allow(clippy::pub_underscore_fields)]
748pub struct GetActiveUniformReturn {
749    pub _0: i32,
750    pub _1: u32,
751    pub _2: AzString,
752}
753
754#[repr(C)]
755pub struct GLsyncPtr {
756    pub ptr: *const c_void, /* *const __GLsync */
757    pub run_destructor: bool,
758}
759
760impl Clone for GLsyncPtr {
761    fn clone(&self) -> Self {
762        Self {
763            ptr: self.ptr,
764            run_destructor: true,
765        }
766    }
767}
768
769impl fmt::Debug for GLsyncPtr {
770    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771        write!(f, "0x{:0x}", self.ptr as usize)
772    }
773}
774
775impl GLsyncPtr {
776    #[must_use] pub const fn new(p: GLsync) -> Self {
777        Self {
778            ptr: p,
779            run_destructor: true,
780        }
781    }
782    #[must_use] pub fn get(self) -> GLsync {
783        self.ptr as GLsync
784    }
785}
786
787impl Drop for GLsyncPtr {
788    fn drop(&mut self) {
789        self.run_destructor = false;
790    }
791}
792
793/// Each pipeline (window) has its own OpenGL textures. GL Textures can technically
794/// be shared across pipelines, however this turns out to be very difficult in practice.
795pub type GlTextureStorage = OrderedMap<Epoch, OrderedMap<ExternalImageId, Texture>>;
796
797/// Non-cleaned up textures. When a `GlTexture` is registered, it has to stay active as long
798/// as `WebRender` needs it for drawing. To transparently do this, we store the epoch that the
799/// texture was originally created with, and check, **after we have drawn the frame**,
800/// if there are any textures that need cleanup.
801///
802/// Because the Texture2d is wrapped in an Rc, the destructor (which cleans up the OpenGL
803/// texture) does not run until we remove the textures
804///
805/// Note: Because textures could be used after the current draw call (ex. for scrolling),
806/// the `ACTIVE_GL_TEXTURES` are indexed by their epoch. Use `renderer.flush_pipeline_info()`
807/// to see which textures are still active and which ones can be safely removed.
808///
809/// See: <https://github.com/servo/webrender/issues/2940>
810///
811/// WARNING: Not thread-safe (however, the Texture itself is thread-unsafe, so it's unlikely to ever
812/// be misused)
813static mut ACTIVE_GL_TEXTURES: Option<OrderedMap<DocumentId, GlTextureStorage>> = None;
814
815/// Sound accessor for the process-global GL texture table.
816///
817/// AUDIT: GL access is single-threaded by design (see the WARNING above — the
818/// `Texture` itself is thread-unsafe), so no lock is used. The soundness fix
819/// here is to never form an *implicit* reference to the `static mut` (the
820/// edition-2024 `static_mut_refs` hard error + `&mut`-aliasing UB that
821/// `ACTIVE_GL_TEXTURES.as_mut()` / `.as_ref()` triggered). Deriving the
822/// reference from `&raw mut` gives it correct provenance without ever naming
823/// the static as an auto-ref place. Callers must not hold two of these at once
824/// (they don't — every use is a single non-reentrant scope).
825#[inline]
826#[allow(clippy::deref_addrof)] // the `&raw mut` deref is deliberate: it avoids naming the static as an auto-ref place (edition-2024 `static_mut_refs`)
827fn active_gl_textures() -> &'static mut Option<OrderedMap<DocumentId, GlTextureStorage>> {
828    // SAFETY: `&raw mut` avoids an intermediate `&mut ACTIVE_GL_TEXTURES`; the
829    // static is valid for the whole program. Single-threaded access (GL thread).
830    unsafe { &mut *(&raw mut ACTIVE_GL_TEXTURES) }
831}
832
833/// Inserts a new texture into the OpenGL texture cache, returns a new image ID
834/// for the inserted texture
835///
836/// This function exists so azul doesn't have to use `lazy_static` as a dependency
837///
838/// # Panics
839///
840/// Panics if the global active-GL-texture table has not been initialized.
841#[must_use]
842pub fn insert_into_active_gl_textures(
843    document_id: DocumentId,
844    epoch: Epoch,
845    texture: Texture,
846) -> ExternalImageId {
847    let external_image_id = ExternalImageId::new();
848
849    let active = active_gl_textures();
850    if active.is_none() {
851        *active = Some(OrderedMap::new());
852    }
853    let active_textures = active.as_mut().unwrap();
854    let active_epochs = active_textures.entry(document_id).or_default();
855    let active_textures_for_epoch = active_epochs.entry(epoch).or_default();
856    active_textures_for_epoch.insert(external_image_id, texture);
857
858    external_image_id
859}
860
861/// Destroys all textures from the given `document_id`
862/// where the texture is **older** than the given `epoch`.
863pub fn gl_textures_remove_epochs_from_pipeline(document_id: &DocumentId, epoch: Epoch) {
864    // TODO: Handle overflow of Epochs correctly (low priority)
865    let Some(active_textures) = active_gl_textures().as_mut() else {
866        return;
867    };
868
869    let Some(active_epochs) = active_textures.get_mut(document_id) else {
870        return;
871    };
872
873    // NOTE: original code used retain() but that
874    // doesn't work on no_std
875    let mut epochs_to_remove = Vec::new();
876
877    for (gl_texture_epoch, _) in active_epochs.iter() {
878        if *gl_texture_epoch < epoch {
879            epochs_to_remove.push(*gl_texture_epoch);
880        }
881    }
882
883    for epoch in epochs_to_remove {
884        active_epochs.remove(&epoch);
885    }
886}
887
888// document_id, epoch, external_image_id
889#[must_use] pub fn remove_single_texture_from_active_gl_textures(
890    document_id: &DocumentId,
891    epoch: &Epoch,
892    external_image_id: &ExternalImageId,
893) -> Option<()> {
894    let active_textures = active_gl_textures().as_mut()?;
895    let epochs = active_textures.get_mut(document_id)?;
896    let images_in_epoch = epochs.get_mut(epoch)?;
897    images_in_epoch.remove(external_image_id);
898    Some(())
899}
900
901/// Removes a `DocumentId` from the active epochs
902pub fn gl_textures_remove_active_pipeline(document_id: &DocumentId) {
903    let Some(active_textures) = active_gl_textures().as_mut() else {
904        return;
905    };
906    active_textures.remove(document_id);
907}
908
909/// Destroys all textures, usually done before destroying the OpenGL context
910#[allow(clippy::cast_precision_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
911pub fn gl_textures_clear_opengl_cache() {
912    *active_gl_textures() = None;
913}
914
915// Search all epoch hash maps for the given key
916// There does not seem to be a way to get the epoch for the key,
917// so we simply have to search all active epochs
918//
919// NOTE: Invalid textures can be generated on minimize / maximize
920// Luckily, webrender simply ignores an invalid texture, so we don't
921// need to check whether a window is maximized or minimized - if
922// we encounter an invalid ID, webrender simply won't draw anything,
923// but at least it won't crash. Usually invalid textures are also 0x0
924// pixels large - so it's not like we had anything to draw anyway.
925#[allow(clippy::cast_precision_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
926#[must_use] pub fn get_opengl_texture(image_key: &ExternalImageId) -> Option<(GLuint, (f32, f32))> {
927    let active_textures = active_gl_textures().as_ref()?;
928    active_textures
929        .values()
930        .flat_map(|active_document| active_document.values())
931        .find_map(|active_epoch| active_epoch.get(image_key))
932        .map(|tex| {
933            (
934                tex.texture_id,
935                (tex.size.width as f32, tex.size.height as f32),
936            )
937        })
938}
939
940/// For .`get_gl_precision_format()`, but ABI-safe - returning an array or a tuple is not ABI-safe
941#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
942#[repr(C)]
943// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
944#[allow(clippy::pub_underscore_fields)]
945pub struct GlShaderPrecisionFormatReturn {
946    pub _0: GLint,
947    pub _1: GLint,
948    pub _2: GLint,
949}
950
951#[repr(C)]
952pub struct GlContextPtr {
953    /// `ManuallyDrop` so the owned `Box` is freed ONLY when `run_destructor` is
954    /// still set (see `Drop`). The codegen FFI wrappers (`AzTexture` etc.) embed
955    /// this by value AND have their own `Drop` that `drop_in_place`s the real
956    /// type first; Rust's drop glue would then drop this field a SECOND time on
957    /// the same bytes. Gating the `Box` free on `run_destructor` (which the first
958    /// drop clears in the shared memory) makes that second drop a safe no-op.
959    /// Layout is unchanged: `ManuallyDrop<Box<T>>` is a single pointer, identical
960    /// to the old `Box<T>` and to the FFI `*mut c_void`.
961    pub ptr: ManuallyDrop<Box<Rc<GlContextPtrInner>>>,
962    /// Whether to force a hardware or software renderer
963    pub renderer_type: RendererType,
964    pub run_destructor: bool,
965}
966
967impl Clone for GlContextPtr {
968    fn clone(&self) -> Self {
969        Self {
970            ptr: ManuallyDrop::new((*self.ptr).clone()),
971            renderer_type: self.renderer_type,
972            run_destructor: true,
973        }
974    }
975}
976
977impl Drop for GlContextPtr {
978    fn drop(&mut self) {
979        // Only free the owned Box if this instance still owns it. The FFI wrapper
980        // double-drop (see the struct doc) hits these same bytes a second time
981        // with `run_destructor` already cleared by the first drop -> no-op, no
982        // double-free.
983        if self.run_destructor {
984            self.run_destructor = false;
985            unsafe { ManuallyDrop::drop(&mut self.ptr); }
986        }
987    }
988}
989
990impl GlContextPtr {
991    #[must_use] pub fn get_svg_shader(&self) -> GLuint {
992        self.ptr.svg_shader
993    }
994    /// Whether this hardware GL context proved usable at construction (the SVG
995    /// shaders compiled+linked at some GLSL version). `false` means context
996    /// creation succeeded but the driver can't run our shaders -- the caller
997    /// should fall back to CPU rendering. Always `false` for a Software context
998    /// (which never compiles these shaders); only meaningful on the GPU path.
999    #[must_use] pub fn is_gl_usable(&self) -> bool {
1000        self.ptr.svg_shader != 0
1001    }
1002    /// The GLSL `#version` the driver accepted at construction (e.g. "150" or
1003    /// "300 es"), discovered by the probe. Empty string if the context is
1004    /// unusable / software. Exposed in the API so apps can report/branch on it.
1005    #[must_use] pub fn get_usable_glsl_version(&self) -> AzString {
1006        self.ptr.glsl_version.clone()
1007    }
1008    /// Soft-brush shader program for the GPU painting API (0 if unusable).
1009    #[must_use] pub fn get_brush_shader(&self) -> GLuint {
1010        self.ptr.brush_shader
1011    }
1012    #[must_use] pub fn get_fxaa_shader(&self) -> GLuint {
1013        self.ptr.fxaa_shader
1014    }
1015}
1016
1017#[repr(C)]
1018pub struct GlContextPtrInner {
1019    pub ptr: Rc<GenericGlContext>,
1020    /// SVG shader program (library-internal use)
1021    pub svg_shader: GLuint,
1022    /// SVG multicolor shader program (library-internal use)
1023    pub svg_multicolor_shader: GLuint,
1024    /// FXAA shader program (library-internal use)
1025    pub fxaa_shader: GLuint,
1026    /// Soft-brush shader program for the GPU painting API (0 if unusable).
1027    pub brush_shader: GLuint,
1028    /// The GLSL `#version` directive that compiled (e.g. "150" or "300 es"),
1029    /// discovered by the probe in `new()`. Empty if the context is unusable.
1030    pub glsl_version: AzString,
1031}
1032
1033impl fmt::Debug for GlContextPtrInner {
1034    // `ptr` wraps the external GL context (not Debug); show the rest.
1035    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1036        f.debug_struct("GlContextPtrInner")
1037            .field("svg_shader", &self.svg_shader)
1038            .field("svg_multicolor_shader", &self.svg_multicolor_shader)
1039            .field("fxaa_shader", &self.fxaa_shader)
1040            .field("brush_shader", &self.brush_shader)
1041            .field("glsl_version", &self.glsl_version)
1042            .finish_non_exhaustive()
1043    }
1044}
1045
1046impl Drop for GlContextPtrInner {
1047    fn drop(&mut self) {
1048        self.ptr.delete_program(self.svg_shader);
1049        self.ptr.delete_program(self.svg_multicolor_shader);
1050        self.ptr.delete_program(self.fxaa_shader);
1051        if self.brush_shader != 0 {
1052            self.ptr.delete_program(self.brush_shader);
1053        }
1054    }
1055}
1056
1057impl_option!(
1058    GlContextPtr,
1059    OptionGlContextPtr,
1060    copy = false,
1061    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord]
1062);
1063
1064impl fmt::Debug for GlContextPtr {
1065    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1066        write!(f, "0x{:0x}", self.as_usize())
1067    }
1068}
1069
1070static SVG_VERTEX_SHADER: &[u8] = b"#version 150
1071
1072#if __VERSION__ != 100
1073    #define varying out
1074    #define attribute in
1075#endif
1076
1077uniform vec2 vBboxSize;
1078uniform mat4 vTransformMatrix;
1079
1080attribute vec2 vAttrXY;
1081
1082void main() {
1083    vec4 vTransposed = vec4(vAttrXY, 1.0, 1.0) * vTransformMatrix;
1084    vec2 vTransposedInScreen = vTransposed.xy / vBboxSize;
1085    vec2 vCalcFinal = (vTransposedInScreen * vec2(2.0)) - vec2(1.0);
1086    gl_Position = vec4(vCalcFinal, 1.0, 1.0);
1087}";
1088
1089static SVG_FRAGMENT_SHADER: &[u8] = b"#version 150
1090
1091precision highp float;
1092
1093uniform vec4 fDrawColor;
1094
1095#if __VERSION__ == 100
1096    #define oFragColor gl_FragColor
1097#else
1098    out vec4 oFragColor;
1099#endif
1100
1101void main() {
1102    oFragColor = fDrawColor;
1103}";
1104
1105static SVG_MULTICOLOR_VERTEX_SHADER: &[u8] = b"#version 150
1106
1107#if __VERSION__ != 100
1108    #define varying out
1109    #define attribute in
1110#endif
1111
1112uniform vec2 vBboxSize;
1113uniform mat4 vTransformMatrix;
1114
1115attribute vec3 vAttrXY;
1116attribute vec4 vColor;
1117varying vec4 fColor;
1118
1119void main() {
1120    vec4 vTransposed = vec4(vAttrXY.xy, 1.0, 1.0) * vTransformMatrix;
1121    vec2 vTransposedInScreen = vTransposed.xy / vBboxSize;
1122    vec2 vCalcFinal = (vTransposedInScreen * vec2(2.0)) - vec2(1.0);
1123    gl_Position = vec4(vCalcFinal, vAttrXY.z, 1.0);
1124    fColor = vColor;
1125}";
1126
1127static SVG_MULTICOLOR_FRAGMENT_SHADER: &[u8] = b"#version 150
1128
1129precision highp float;
1130
1131#if __VERSION__ != 100
1132    #define varying in
1133#endif
1134
1135#if __VERSION__ == 100
1136    #define oFragColor gl_FragColor
1137#else
1138    out vec4 oFragColor;
1139#endif
1140
1141varying vec4 fColor;
1142
1143void main() {
1144    oFragColor = fColor;
1145}";
1146
1147// Soft-brush shaders for the GPU painting API. A unit quad [-1,1]^2 (aUv) is
1148// positioned in NDC (aPos); the fragment computes the same radial falloff as
1149// the CPU `brush_dab_coverage` (1 - smoothstep(hardness, 1, dist)) so GPU and
1150// CPU strokes match. Version-agnostic via `__VERSION__` like the SVG shaders.
1151static BRUSH_VERTEX_SHADER: &[u8] = b"#version 150
1152
1153#if __VERSION__ != 100
1154    #define varying out
1155    #define attribute in
1156#endif
1157
1158attribute vec2 aPos;
1159attribute vec2 aUv;
1160varying vec2 vUv;
1161
1162void main() {
1163    vUv = aUv;
1164    gl_Position = vec4(aPos, 0.0, 1.0);
1165}";
1166
1167static BRUSH_FRAGMENT_SHADER: &[u8] = b"#version 150
1168
1169precision highp float;
1170
1171#if __VERSION__ != 100
1172    #define varying in
1173#endif
1174
1175#if __VERSION__ == 100
1176    #define oFragColor gl_FragColor
1177#else
1178    out vec4 oFragColor;
1179#endif
1180
1181uniform vec4 uColor;     // rgb + alpha (alpha already folds in flow * color.a)
1182uniform float uHardness; // 0 = soft .. 1 = hard edge
1183
1184varying vec2 vUv;
1185
1186void main() {
1187    float d = length(vUv);
1188    if (d > 1.0) { discard; }
1189    float edge0 = clamp(uHardness, 0.0, 1.0);
1190    float x = clamp((d - edge0) / max(1.0 - edge0, 1.0e-4), 0.0, 1.0);
1191    float cov = 1.0 - (x * x * (3.0 - 2.0 * x));
1192    oFragColor = vec4(uColor.rgb, uColor.a * cov);
1193}";
1194
1195/// Checks if a shader compiled successfully. Logs an error under `std`.
1196/// (Retained for diagnostics; the version probe in `GlContextPtr::new` now does
1197/// its own status checks.)
1198#[allow(dead_code)]
1199#[allow(clippy::used_underscore_binding)] // intentional `_`-prefix (FFI/api.json pub field, or cfg-gated binding); access is deliberate
1200#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
1201fn check_shader_compile(gl_context: &GenericGlContext, shader: GLuint, _label: &str) {
1202    let mut status = [0_i32];
1203    unsafe { gl_context.get_shader_iv(shader, gl::COMPILE_STATUS, &mut status) };
1204    if status[0] != gl::TRUE as i32 {
1205        #[cfg(feature = "std")]
1206        {
1207            let log = gl_context.get_shader_info_log(shader);
1208            eprintln!("azul: {_label} shader compile error: {log}");
1209        }
1210    }
1211}
1212
1213/// Checks if a program linked successfully. Logs an error under `std`.
1214#[allow(dead_code)]
1215#[allow(clippy::used_underscore_binding)] // intentional `_`-prefix (FFI/api.json pub field, or cfg-gated binding); access is deliberate
1216#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
1217fn check_program_link(gl_context: &GenericGlContext, program: GLuint, _label: &str) {
1218    let mut status = [0_i32];
1219    unsafe { gl_context.get_program_iv(program, gl::LINK_STATUS, &mut status) };
1220    if status[0] != gl::TRUE as i32 {
1221        #[cfg(feature = "std")]
1222        {
1223            let log = gl_context.get_program_info_log(program);
1224            eprintln!("azul: {_label} program link error: {log}");
1225        }
1226    }
1227}
1228
1229/// Swap the leading `#version ...` line of a bundled shader for `version_line`
1230/// (which must include the trailing newline). The shader bodies branch on
1231/// `__VERSION__`, so only the directive needs to change between GL and GLES.
1232#[cfg(feature = "std")]
1233fn shader_with_glsl_version(src: &[u8], version_line: &[u8]) -> Vec<u8> {
1234    let body_start = src.iter().position(|&b| b == b'\n').map_or(0, |i| i + 1);
1235    let mut out = Vec::with_capacity(version_line.len() + src.len() - body_start);
1236    out.extend_from_slice(version_line);
1237    out.extend_from_slice(&src[body_start..]);
1238    out
1239}
1240
1241/// Try to compile+link a vertex+fragment program at a specific GLSL `#version`.
1242/// Returns the linked program id, or `None` (after cleanup) on ANY compile or
1243/// link failure. This is how we PROVE a GL context is actually usable and which
1244/// `#version` its driver accepts -- creating a context can succeed yet leave it
1245/// unable to compile our shaders (broken driver, or a GLES context that rejects
1246/// the desktop `#version 150`).
1247#[cfg(feature = "std")]
1248// OpenGL binding: gl::* enum constants passed to the gl API as GLint/GLenum.
1249#[allow(clippy::cast_possible_wrap)]
1250fn try_compile_program(
1251    gl_context: &GenericGlContext,
1252    vert_src: &[u8],
1253    frag_src: &[u8],
1254    version_line: &[u8],
1255    attribs: &[(u32, &str)],
1256) -> Option<GLuint> {
1257    let vs = gl_context.create_shader(gl::VERTEX_SHADER);
1258    gl_context.shader_source(vs, &[shader_with_glsl_version(vert_src, version_line).as_slice()]);
1259    gl_context.compile_shader(vs);
1260    let fs = gl_context.create_shader(gl::FRAGMENT_SHADER);
1261    gl_context.shader_source(fs, &[shader_with_glsl_version(frag_src, version_line).as_slice()]);
1262    gl_context.compile_shader(fs);
1263
1264    let mut s = [0_i32];
1265    unsafe { gl_context.get_shader_iv(vs, gl::COMPILE_STATUS, &mut s) };
1266    let vs_ok = s[0] == gl::TRUE as i32;
1267    unsafe { gl_context.get_shader_iv(fs, gl::COMPILE_STATUS, &mut s) };
1268    let fs_ok = s[0] == gl::TRUE as i32;
1269    if !vs_ok || !fs_ok {
1270        gl_context.delete_shader(vs);
1271        gl_context.delete_shader(fs);
1272        return None;
1273    }
1274
1275    let prog = gl_context.create_program();
1276    gl_context.attach_shader(prog, vs);
1277    gl_context.attach_shader(prog, fs);
1278    for (loc, name) in attribs {
1279        gl_context.bind_attrib_location(prog, *loc, name);
1280    }
1281    gl_context.link_program(prog);
1282    gl_context.delete_shader(vs);
1283    gl_context.delete_shader(fs);
1284
1285    let mut l = [0_i32];
1286    unsafe { gl_context.get_program_iv(prog, gl::LINK_STATUS, &mut l) };
1287    if l[0] == gl::TRUE as i32 {
1288        Some(prog)
1289    } else {
1290        gl_context.delete_program(prog);
1291        None
1292    }
1293}
1294
1295/// GLSL `#version` directives to try, in preference order, per context type.
1296/// The first that compiles+links the SVG shaders is used for every program.
1297const fn glsl_version_candidates(gl_type: GlType) -> &'static [&'static [u8]] {
1298    match gl_type {
1299        GlType::Gl => &[b"#version 150\n", b"#version 330\n", b"#version 140\n"],
1300        GlType::Gles => &[b"#version 300 es\n", b"#version 100\n"],
1301    }
1302}
1303
1304impl GlContextPtr {
1305    #[must_use] pub fn new(renderer_type: RendererType, gl_context: Rc<GenericGlContext>) -> Self {
1306        // Only attempt the SVG/FXAA GL shaders for a real GPU. In Software/CPU
1307        // mode nothing composites through them.
1308        //
1309        // PROVE the context is usable rather than trusting context creation: try
1310        // compiling the SVG program at each candidate `#version` for this context
1311        // type (desktop GL 1.50/3.30/1.40, or GLES 3.00/1.00) and use the first
1312        // that compiles+links for ALL programs. A GLES GPU (mobile) rejects the
1313        // desktop `#version 150`, and a broken driver rejects everything -- in the
1314        // latter case all program IDs stay 0 and `is_gl_usable()` returns false so
1315        // the window can fall back to CPU rendering.
1316        #[cfg(feature = "std")]
1317        let (svg_program_id, svg_multicolor_program_id, fxaa_program_id, brush_program_id, glsl_version) =
1318            if matches!(renderer_type, RendererType::Hardware) {
1319                use crate::gl_fxaa::{FXAA_FRAGMENT_SHADER, FXAA_VERTEX_SHADER};
1320                let gl_type: GlType = gl_context.get_type().into();
1321                // Probe via the SVG program; the first version that links wins.
1322                let mut svg = 0;
1323                let mut chosen: Option<&'static [u8]> = None;
1324                for ver in glsl_version_candidates(gl_type) {
1325                    if let Some(p) = try_compile_program(
1326                        &gl_context, SVG_VERTEX_SHADER, SVG_FRAGMENT_SHADER, ver, &[(0, "vAttrXY")],
1327                    ) {
1328                        svg = p;
1329                        chosen = Some(ver);
1330                        break;
1331                    }
1332                }
1333                chosen.map_or_else(|| {
1334                    eprintln!(
1335                        "azul: GL context UNUSABLE -- no GLSL version ({gl_type:?}) compiled the SVG \
1336                         shaders; the window should fall back to CPU rendering (is_gl_usable()=false)"
1337                    );
1338                    (0, 0, 0, 0, AzString::from_const_str(""))
1339                }, |ver| {
1340                    // "150" / "300 es": the directive minus "#version " and newline.
1341                    let ver_str: AzString = core::str::from_utf8(ver)
1342                        .unwrap_or("")
1343                        .trim()
1344                        .trim_start_matches("#version ")
1345                        .into();
1346                    eprintln!(
1347                        "azul: GL usable -- shaders compiled at GLSL {} ({:?})",
1348                        ver_str.as_str(),
1349                        gl_type
1350                    );
1351                    let mc = try_compile_program(
1352                        &gl_context, SVG_MULTICOLOR_VERTEX_SHADER, SVG_MULTICOLOR_FRAGMENT_SHADER,
1353                        ver, &[(0, "vAttrXY"), (1, "vColor")],
1354                    ).unwrap_or(0);
1355                    let fxaa = try_compile_program(
1356                        &gl_context, FXAA_VERTEX_SHADER, FXAA_FRAGMENT_SHADER, ver, &[(0, "vAttrXY")],
1357                    ).unwrap_or(0);
1358                    let brush = try_compile_program(
1359                        &gl_context, BRUSH_VERTEX_SHADER, BRUSH_FRAGMENT_SHADER, ver,
1360                        &[(0, "aPos"), (1, "aUv")],
1361                    ).unwrap_or(0);
1362                    (svg, mc, fxaa, brush, ver_str)
1363                })
1364            } else {
1365                (0, 0, 0, 0, AzString::from_const_str(""))
1366            };
1367        // no_std build keeps the original behavior (no probe / no shaders).
1368        #[cfg(not(feature = "std"))]
1369        let (svg_program_id, svg_multicolor_program_id, fxaa_program_id, brush_program_id, glsl_version) =
1370            (0u32, 0u32, 0u32, 0u32, AzString::from_const_str(""));
1371
1372        
1373        Self {
1374            ptr: ManuallyDrop::new(Box::new(Rc::new(GlContextPtrInner {
1375                svg_shader: svg_program_id,
1376                svg_multicolor_shader: svg_multicolor_program_id,
1377                fxaa_shader: fxaa_program_id,
1378                brush_shader: brush_program_id,
1379                glsl_version,
1380                ptr: gl_context,
1381            }))),
1382            renderer_type,
1383            run_destructor: true,
1384        }
1385    }
1386
1387    #[must_use] pub fn get(&self) -> &Rc<GenericGlContext> {
1388        &self.ptr.ptr
1389    }
1390    fn as_usize(&self) -> usize {
1391        (Rc::as_ptr(&self.ptr.ptr) as *const c_void) as usize
1392    }
1393}
1394
1395// This impl is the OpenGL API wrapper: every method mirrors a C/gleam GL call and
1396// takes the C-ABI argument types (GlVoidPtrConst, *VecRef, …) BY VALUE to match that
1397// ABI/FFI calling convention. Switching them to references would break the contract,
1398// so needless_pass_by_value is allowed for the whole GL-binding impl.
1399#[allow(clippy::needless_pass_by_value)]
1400impl GlContextPtr {
1401    #[must_use] pub fn get_type(&self) -> GlType {
1402        self.get().get_type().into()
1403    }
1404    pub fn buffer_data_untyped(
1405        &self,
1406        target: GLenum,
1407        size: GLsizeiptr,
1408        data: GlVoidPtrConst,
1409        usage: GLenum,
1410    ) {
1411        self.get()
1412            .buffer_data_untyped(target, size, data.ptr, usage);
1413    }
1414    pub fn buffer_sub_data_untyped(
1415        &self,
1416        target: GLenum,
1417        offset: isize,
1418        size: GLsizeiptr,
1419        data: GlVoidPtrConst,
1420    ) {
1421        self.get()
1422            .buffer_sub_data_untyped(target, offset, size, data.ptr);
1423    }
1424    #[must_use] pub fn map_buffer(&self, target: GLenum, access: GLbitfield) -> GlVoidPtrMut {
1425        GlVoidPtrMut {
1426            ptr: self.get().map_buffer(target, access),
1427        }
1428    }
1429    #[must_use] pub fn map_buffer_range(
1430        &self,
1431        target: GLenum,
1432        offset: GLintptr,
1433        length: GLsizeiptr,
1434        access: GLbitfield,
1435    ) -> GlVoidPtrMut {
1436        GlVoidPtrMut {
1437            ptr: self.get().map_buffer_range(target, offset, length, access),
1438        }
1439    }
1440    #[must_use] pub fn unmap_buffer(&self, target: GLenum) -> GLboolean {
1441        self.get().unmap_buffer(target)
1442    }
1443    pub fn tex_buffer(&self, target: GLenum, internal_format: GLenum, buffer: GLuint) {
1444        self.get().tex_buffer(target, internal_format, buffer);
1445    }
1446    pub fn shader_source(&self, shader: GLuint, strings: StringVec) {
1447        fn str_to_bytes(input: &str) -> Vec<u8> {
1448            let mut v: Vec<u8> = input.into();
1449            v.push(0);
1450            v
1451        }
1452        let shaders_as_bytes = strings
1453            .iter()
1454            .map(|s| str_to_bytes(s.as_str()))
1455            .collect::<Vec<_>>();
1456        let shaders_as_bytes = shaders_as_bytes
1457            .iter()
1458            .map(AsRef::as_ref)
1459            .collect::<Vec<_>>();
1460        self.get().shader_source(shader, &shaders_as_bytes);
1461    }
1462    pub fn read_buffer(&self, mode: GLenum) {
1463        self.get().read_buffer(mode);
1464    }
1465    pub fn read_pixels_into_buffer(
1466        &self,
1467        x: GLint,
1468        y: GLint,
1469        width: GLsizei,
1470        height: GLsizei,
1471        format: GLenum,
1472        pixel_type: GLenum,
1473        mut dst_buffer: U8VecRefMut,
1474    ) {
1475        self.get().read_pixels_into_buffer(
1476            x,
1477            y,
1478            width,
1479            height,
1480            format,
1481            pixel_type,
1482            dst_buffer.as_mut_slice(),
1483        );
1484    }
1485    #[must_use] pub fn read_pixels(
1486        &self,
1487        x: GLint,
1488        y: GLint,
1489        width: GLsizei,
1490        height: GLsizei,
1491        format: GLenum,
1492        pixel_type: GLenum,
1493    ) -> U8Vec {
1494        self.get()
1495            .read_pixels(x, y, width, height, format, pixel_type)
1496            .into()
1497    }
1498    pub fn read_pixels_into_pbo(
1499        &self,
1500        x: GLint,
1501        y: GLint,
1502        width: GLsizei,
1503        height: GLsizei,
1504        format: GLenum,
1505        pixel_type: GLenum,
1506    ) {
1507        unsafe {
1508            self.get()
1509                .read_pixels_into_pbo(x, y, width, height, format, pixel_type);
1510        }
1511    }
1512    pub fn sample_coverage(&self, value: GLclampf, invert: bool) {
1513        self.get().sample_coverage(value, invert);
1514    }
1515    pub fn polygon_offset(&self, factor: GLfloat, units: GLfloat) {
1516        self.get().polygon_offset(factor, units);
1517    }
1518    pub fn pixel_store_i(&self, name: GLenum, param: GLint) {
1519        self.get().pixel_store_i(name, param);
1520    }
1521    #[must_use] pub fn gen_buffers(&self, n: GLsizei) -> GLuintVec {
1522        self.get().gen_buffers(n).into()
1523    }
1524    #[must_use] pub fn gen_renderbuffers(&self, n: GLsizei) -> GLuintVec {
1525        self.get().gen_renderbuffers(n).into()
1526    }
1527    #[must_use] pub fn gen_framebuffers(&self, n: GLsizei) -> GLuintVec {
1528        self.get().gen_framebuffers(n).into()
1529    }
1530    #[must_use] pub fn gen_textures(&self, n: GLsizei) -> GLuintVec {
1531        self.get().gen_textures(n).into()
1532    }
1533    #[must_use] pub fn gen_vertex_arrays(&self, n: GLsizei) -> GLuintVec {
1534        self.get().gen_vertex_arrays(n).into()
1535    }
1536    #[must_use] pub fn gen_queries(&self, n: GLsizei) -> GLuintVec {
1537        self.get().gen_queries(n).into()
1538    }
1539    pub fn begin_query(&self, target: GLenum, id: GLuint) {
1540        self.get().begin_query(target, id);
1541    }
1542    pub fn end_query(&self, target: GLenum) {
1543        self.get().end_query(target);
1544    }
1545    pub fn query_counter(&self, id: GLuint, target: GLenum) {
1546        self.get().query_counter(id, target);
1547    }
1548    #[must_use] pub fn get_query_object_iv(&self, id: GLuint, pname: GLenum) -> i32 {
1549        self.get().get_query_object_iv(id, pname)
1550    }
1551    #[must_use] pub fn get_query_object_uiv(&self, id: GLuint, pname: GLenum) -> u32 {
1552        self.get().get_query_object_uiv(id, pname)
1553    }
1554    #[must_use] pub fn get_query_object_i64v(&self, id: GLuint, pname: GLenum) -> i64 {
1555        self.get().get_query_object_i64v(id, pname)
1556    }
1557    #[must_use] pub fn get_query_object_ui64v(&self, id: GLuint, pname: GLenum) -> u64 {
1558        self.get().get_query_object_ui64v(id, pname)
1559    }
1560    pub fn delete_queries(&self, queries: GLuintVecRef) {
1561        self.get().delete_queries(queries.as_slice());
1562    }
1563    pub fn delete_vertex_arrays(&self, vertex_arrays: GLuintVecRef) {
1564        self.get().delete_vertex_arrays(vertex_arrays.as_slice());
1565    }
1566    pub fn delete_buffers(&self, buffers: GLuintVecRef) {
1567        self.get().delete_buffers(buffers.as_slice());
1568    }
1569    pub fn delete_renderbuffers(&self, renderbuffers: GLuintVecRef) {
1570        self.get().delete_renderbuffers(renderbuffers.as_slice());
1571    }
1572    pub fn delete_framebuffers(&self, framebuffers: GLuintVecRef) {
1573        self.get().delete_framebuffers(framebuffers.as_slice());
1574    }
1575    pub fn delete_textures(&self, textures: GLuintVecRef) {
1576        self.get().delete_textures(textures.as_slice());
1577    }
1578    pub fn framebuffer_renderbuffer(
1579        &self,
1580        target: GLenum,
1581        attachment: GLenum,
1582        renderbuffertarget: GLenum,
1583        renderbuffer: GLuint,
1584    ) {
1585        self.get()
1586            .framebuffer_renderbuffer(target, attachment, renderbuffertarget, renderbuffer);
1587    }
1588    pub fn renderbuffer_storage(
1589        &self,
1590        target: GLenum,
1591        internalformat: GLenum,
1592        width: GLsizei,
1593        height: GLsizei,
1594    ) {
1595        self.get()
1596            .renderbuffer_storage(target, internalformat, width, height);
1597    }
1598    pub fn depth_func(&self, func: GLenum) {
1599        self.get().depth_func(func);
1600    }
1601    pub fn active_texture(&self, texture: GLenum) {
1602        self.get().active_texture(texture);
1603    }
1604    pub fn attach_shader(&self, program: GLuint, shader: GLuint) {
1605        self.get().attach_shader(program, shader);
1606    }
1607    pub fn bind_attrib_location(&self, program: GLuint, index: GLuint, name: &str) {
1608        self.get()
1609            .bind_attrib_location(program, index, name);
1610    }
1611    pub fn get_uniform_iv(&self, program: GLuint, location: GLint, mut result: GLintVecRefMut) {
1612        unsafe {
1613            self.get()
1614                .get_uniform_iv(program, location, result.as_mut_slice());
1615        }
1616    }
1617    pub fn get_uniform_fv(&self, program: GLuint, location: GLint, mut result: GLfloatVecRefMut) {
1618        unsafe {
1619            self.get()
1620                .get_uniform_fv(program, location, result.as_mut_slice());
1621        }
1622    }
1623    #[must_use] pub fn get_uniform_block_index(&self, program: GLuint, name: &str) -> GLuint {
1624        self.get().get_uniform_block_index(program, name)
1625    }
1626    #[must_use] pub fn get_uniform_indices(&self, program: GLuint, names: RefstrVecRef) -> GLuintVec {
1627        let names_vec = names
1628            .as_slice()
1629            .iter()
1630            .map(Refstr::as_str)
1631            .collect::<Vec<_>>();
1632        self.get().get_uniform_indices(program, &names_vec).into()
1633    }
1634    pub fn bind_buffer_base(&self, target: GLenum, index: GLuint, buffer: GLuint) {
1635        self.get().bind_buffer_base(target, index, buffer);
1636    }
1637    pub fn bind_buffer_range(
1638        &self,
1639        target: GLenum,
1640        index: GLuint,
1641        buffer: GLuint,
1642        offset: GLintptr,
1643        size: GLsizeiptr,
1644    ) {
1645        self.get()
1646            .bind_buffer_range(target, index, buffer, offset, size);
1647    }
1648    pub fn uniform_block_binding(
1649        &self,
1650        program: GLuint,
1651        uniform_block_index: GLuint,
1652        uniform_block_binding: GLuint,
1653    ) {
1654        self.get()
1655            .uniform_block_binding(program, uniform_block_index, uniform_block_binding);
1656    }
1657    pub fn bind_buffer(&self, target: GLenum, buffer: GLuint) {
1658        self.get().bind_buffer(target, buffer);
1659    }
1660    pub fn bind_vertex_array(&self, vao: GLuint) {
1661        self.get().bind_vertex_array(vao);
1662    }
1663    pub fn bind_renderbuffer(&self, target: GLenum, renderbuffer: GLuint) {
1664        self.get().bind_renderbuffer(target, renderbuffer);
1665    }
1666    pub fn bind_framebuffer(&self, target: GLenum, framebuffer: GLuint) {
1667        self.get().bind_framebuffer(target, framebuffer);
1668    }
1669    pub fn bind_texture(&self, target: GLenum, texture: GLuint) {
1670        self.get().bind_texture(target, texture);
1671    }
1672    pub fn draw_buffers(&self, bufs: GLenumVecRef) {
1673        self.get().draw_buffers(bufs.as_slice());
1674    }
1675    pub fn tex_image_2d(
1676        &self,
1677        target: GLenum,
1678        level: GLint,
1679        internal_format: GLint,
1680        width: GLsizei,
1681        height: GLsizei,
1682        border: GLint,
1683        format: GLenum,
1684        ty: GLenum,
1685        opt_data: OptionU8VecRef,
1686    ) {
1687        let opt_data = opt_data.as_option();
1688        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
1689        self.get().tex_image_2d(
1690            target,
1691            level,
1692            internal_format,
1693            width,
1694            height,
1695            border,
1696            format,
1697            ty,
1698            opt_data,
1699        );
1700    }
1701    pub fn compressed_tex_image_2d(
1702        &self,
1703        target: GLenum,
1704        level: GLint,
1705        internal_format: GLenum,
1706        width: GLsizei,
1707        height: GLsizei,
1708        border: GLint,
1709        data: U8VecRef,
1710    ) {
1711        self.get().compressed_tex_image_2d(
1712            target,
1713            level,
1714            internal_format,
1715            width,
1716            height,
1717            border,
1718            data.as_slice(),
1719        );
1720    }
1721    pub fn compressed_tex_sub_image_2d(
1722        &self,
1723        target: GLenum,
1724        level: GLint,
1725        xoffset: GLint,
1726        yoffset: GLint,
1727        width: GLsizei,
1728        height: GLsizei,
1729        format: GLenum,
1730        data: U8VecRef,
1731    ) {
1732        self.get().compressed_tex_sub_image_2d(
1733            target,
1734            level,
1735            xoffset,
1736            yoffset,
1737            width,
1738            height,
1739            format,
1740            data.as_slice(),
1741        );
1742    }
1743    pub fn tex_image_3d(
1744        &self,
1745        target: GLenum,
1746        level: GLint,
1747        internal_format: GLint,
1748        width: GLsizei,
1749        height: GLsizei,
1750        depth: GLsizei,
1751        border: GLint,
1752        format: GLenum,
1753        ty: GLenum,
1754        opt_data: OptionU8VecRef,
1755    ) {
1756        let opt_data = opt_data.as_option();
1757        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
1758        self.get().tex_image_3d(
1759            target,
1760            level,
1761            internal_format,
1762            width,
1763            height,
1764            depth,
1765            border,
1766            format,
1767            ty,
1768            opt_data,
1769        );
1770    }
1771    pub fn copy_tex_image_2d(
1772        &self,
1773        target: GLenum,
1774        level: GLint,
1775        internal_format: GLenum,
1776        x: GLint,
1777        y: GLint,
1778        width: GLsizei,
1779        height: GLsizei,
1780        border: GLint,
1781    ) {
1782        self.get()
1783            .copy_tex_image_2d(target, level, internal_format, x, y, width, height, border);
1784    }
1785    pub fn copy_tex_sub_image_2d(
1786        &self,
1787        target: GLenum,
1788        level: GLint,
1789        xoffset: GLint,
1790        yoffset: GLint,
1791        x: GLint,
1792        y: GLint,
1793        width: GLsizei,
1794        height: GLsizei,
1795    ) {
1796        self.get()
1797            .copy_tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height);
1798    }
1799    pub fn copy_tex_sub_image_3d(
1800        &self,
1801        target: GLenum,
1802        level: GLint,
1803        xoffset: GLint,
1804        yoffset: GLint,
1805        zoffset: GLint,
1806        x: GLint,
1807        y: GLint,
1808        width: GLsizei,
1809        height: GLsizei,
1810    ) {
1811        self.get().copy_tex_sub_image_3d(
1812            target, level, xoffset, yoffset, zoffset, x, y, width, height,
1813        );
1814    }
1815    pub fn tex_sub_image_2d(
1816        &self,
1817        target: GLenum,
1818        level: GLint,
1819        xoffset: GLint,
1820        yoffset: GLint,
1821        width: GLsizei,
1822        height: GLsizei,
1823        format: GLenum,
1824        ty: GLenum,
1825        data: U8VecRef,
1826    ) {
1827        self.get().tex_sub_image_2d(
1828            target,
1829            level,
1830            xoffset,
1831            yoffset,
1832            width,
1833            height,
1834            format,
1835            ty,
1836            data.as_slice(),
1837        );
1838    }
1839    pub fn tex_sub_image_2d_pbo(
1840        &self,
1841        target: GLenum,
1842        level: GLint,
1843        xoffset: GLint,
1844        yoffset: GLint,
1845        width: GLsizei,
1846        height: GLsizei,
1847        format: GLenum,
1848        ty: GLenum,
1849        offset: usize,
1850    ) {
1851        self.get().tex_sub_image_2d_pbo(
1852            target, level, xoffset, yoffset, width, height, format, ty, offset,
1853        );
1854    }
1855    pub fn tex_sub_image_3d(
1856        &self,
1857        target: GLenum,
1858        level: GLint,
1859        xoffset: GLint,
1860        yoffset: GLint,
1861        zoffset: GLint,
1862        width: GLsizei,
1863        height: GLsizei,
1864        depth: GLsizei,
1865        format: GLenum,
1866        ty: GLenum,
1867        data: U8VecRef,
1868    ) {
1869        self.get().tex_sub_image_3d(
1870            target,
1871            level,
1872            xoffset,
1873            yoffset,
1874            zoffset,
1875            width,
1876            height,
1877            depth,
1878            format,
1879            ty,
1880            data.as_slice(),
1881        );
1882    }
1883    pub fn tex_sub_image_3d_pbo(
1884        &self,
1885        target: GLenum,
1886        level: GLint,
1887        xoffset: GLint,
1888        yoffset: GLint,
1889        zoffset: GLint,
1890        width: GLsizei,
1891        height: GLsizei,
1892        depth: GLsizei,
1893        format: GLenum,
1894        ty: GLenum,
1895        offset: usize,
1896    ) {
1897        self.get().tex_sub_image_3d_pbo(
1898            target, level, xoffset, yoffset, zoffset, width, height, depth, format, ty, offset,
1899        );
1900    }
1901    pub fn tex_storage_2d(
1902        &self,
1903        target: GLenum,
1904        levels: GLint,
1905        internal_format: GLenum,
1906        width: GLsizei,
1907        height: GLsizei,
1908    ) {
1909        self.get()
1910            .tex_storage_2d(target, levels, internal_format, width, height);
1911    }
1912    pub fn tex_storage_3d(
1913        &self,
1914        target: GLenum,
1915        levels: GLint,
1916        internal_format: GLenum,
1917        width: GLsizei,
1918        height: GLsizei,
1919        depth: GLsizei,
1920    ) {
1921        self.get()
1922            .tex_storage_3d(target, levels, internal_format, width, height, depth);
1923    }
1924    pub fn get_tex_image_into_buffer(
1925        &self,
1926        target: GLenum,
1927        level: GLint,
1928        format: GLenum,
1929        ty: GLenum,
1930        mut output: U8VecRefMut,
1931    ) {
1932        self.get()
1933            .get_tex_image_into_buffer(target, level, format, ty, output.as_mut_slice());
1934    }
1935    pub fn copy_image_sub_data(
1936        &self,
1937        src_name: GLuint,
1938        src_target: GLenum,
1939        src_level: GLint,
1940        src_x: GLint,
1941        src_y: GLint,
1942        src_z: GLint,
1943        dst_name: GLuint,
1944        dst_target: GLenum,
1945        dst_level: GLint,
1946        dst_x: GLint,
1947        dst_y: GLint,
1948        dst_z: GLint,
1949        src_width: GLsizei,
1950        src_height: GLsizei,
1951        src_depth: GLsizei,
1952    ) {
1953        unsafe {
1954            self.get().copy_image_sub_data(
1955                src_name, src_target, src_level, src_x, src_y, src_z, dst_name, dst_target,
1956                dst_level, dst_x, dst_y, dst_z, src_width, src_height, src_depth,
1957            );
1958        }
1959    }
1960    pub fn invalidate_framebuffer(&self, target: GLenum, attachments: GLenumVecRef) {
1961        self.get()
1962            .invalidate_framebuffer(target, attachments.as_slice());
1963    }
1964    pub fn invalidate_sub_framebuffer(
1965        &self,
1966        target: GLenum,
1967        attachments: GLenumVecRef,
1968        xoffset: GLint,
1969        yoffset: GLint,
1970        width: GLsizei,
1971        height: GLsizei,
1972    ) {
1973        self.get().invalidate_sub_framebuffer(
1974            target,
1975            attachments.as_slice(),
1976            xoffset,
1977            yoffset,
1978            width,
1979            height,
1980        );
1981    }
1982    pub fn get_integer_v(&self, name: GLenum, mut result: GLintVecRefMut) {
1983        unsafe { self.get().get_integer_v(name, result.as_mut_slice()) }
1984    }
1985    pub fn get_integer_64v(&self, name: GLenum, mut result: GLint64VecRefMut) {
1986        unsafe { self.get().get_integer_64v(name, result.as_mut_slice()) }
1987    }
1988    pub fn get_integer_iv(&self, name: GLenum, index: GLuint, mut result: GLintVecRefMut) {
1989        unsafe {
1990            self.get()
1991                .get_integer_iv(name, index, result.as_mut_slice());
1992        }
1993    }
1994    pub fn get_integer_64iv(&self, name: GLenum, index: GLuint, mut result: GLint64VecRefMut) {
1995        unsafe {
1996            self.get()
1997                .get_integer_64iv(name, index, result.as_mut_slice());
1998        }
1999    }
2000    pub fn get_boolean_v(&self, name: GLenum, mut result: GLbooleanVecRefMut) {
2001        unsafe { self.get().get_boolean_v(name, result.as_mut_slice()) }
2002    }
2003    pub fn get_float_v(&self, name: GLenum, mut result: GLfloatVecRefMut) {
2004        unsafe { self.get().get_float_v(name, result.as_mut_slice()) }
2005    }
2006    #[must_use] pub fn get_framebuffer_attachment_parameter_iv(
2007        &self,
2008        target: GLenum,
2009        attachment: GLenum,
2010        pname: GLenum,
2011    ) -> GLint {
2012        self.get()
2013            .get_framebuffer_attachment_parameter_iv(target, attachment, pname)
2014    }
2015    #[must_use] pub fn get_renderbuffer_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint {
2016        self.get().get_renderbuffer_parameter_iv(target, pname)
2017    }
2018    #[must_use] pub fn get_tex_parameter_iv(&self, target: GLenum, name: GLenum) -> GLint {
2019        self.get().get_tex_parameter_iv(target, name)
2020    }
2021    #[must_use] pub fn get_tex_parameter_fv(&self, target: GLenum, name: GLenum) -> GLfloat {
2022        self.get().get_tex_parameter_fv(target, name)
2023    }
2024    pub fn tex_parameter_i(&self, target: GLenum, pname: GLenum, param: GLint) {
2025        self.get().tex_parameter_i(target, pname, param);
2026    }
2027    pub fn tex_parameter_f(&self, target: GLenum, pname: GLenum, param: GLfloat) {
2028        self.get().tex_parameter_f(target, pname, param);
2029    }
2030    pub fn framebuffer_texture_2d(
2031        &self,
2032        target: GLenum,
2033        attachment: GLenum,
2034        textarget: GLenum,
2035        texture: GLuint,
2036        level: GLint,
2037    ) {
2038        self.get()
2039            .framebuffer_texture_2d(target, attachment, textarget, texture, level);
2040    }
2041    pub fn framebuffer_texture_layer(
2042        &self,
2043        target: GLenum,
2044        attachment: GLenum,
2045        texture: GLuint,
2046        level: GLint,
2047        layer: GLint,
2048    ) {
2049        self.get()
2050            .framebuffer_texture_layer(target, attachment, texture, level, layer);
2051    }
2052    #[allow(clippy::similar_names)] // domain-standard coordinate/control-point names
2053    pub fn blit_framebuffer(
2054        &self,
2055        src_x0: GLint,
2056        src_y0: GLint,
2057        src_x1: GLint,
2058        src_y1: GLint,
2059        dst_x0: GLint,
2060        dst_y0: GLint,
2061        dst_x1: GLint,
2062        dst_y1: GLint,
2063        mask: GLbitfield,
2064        filter: GLenum,
2065    ) {
2066        self.get().blit_framebuffer(
2067            src_x0, src_y0, src_x1, src_y1, dst_x0, dst_y0, dst_x1, dst_y1, mask, filter,
2068        );
2069    }
2070    pub fn vertex_attrib_4f(&self, index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
2071        self.get().vertex_attrib_4f(index, x, y, z, w);
2072    }
2073    pub fn vertex_attrib_pointer_f32(
2074        &self,
2075        index: GLuint,
2076        size: GLint,
2077        normalized: bool,
2078        stride: GLsizei,
2079        offset: GLuint,
2080    ) {
2081        self.get()
2082            .vertex_attrib_pointer_f32(index, size, normalized, stride, offset);
2083    }
2084    pub fn vertex_attrib_pointer(
2085        &self,
2086        index: GLuint,
2087        size: GLint,
2088        type_: GLenum,
2089        normalized: bool,
2090        stride: GLsizei,
2091        offset: GLuint,
2092    ) {
2093        self.get()
2094            .vertex_attrib_pointer(index, size, type_, normalized, stride, offset);
2095    }
2096    pub fn vertex_attrib_i_pointer(
2097        &self,
2098        index: GLuint,
2099        size: GLint,
2100        type_: GLenum,
2101        stride: GLsizei,
2102        offset: GLuint,
2103    ) {
2104        self.get()
2105            .vertex_attrib_i_pointer(index, size, type_, stride, offset);
2106    }
2107    pub fn vertex_attrib_divisor(&self, index: GLuint, divisor: GLuint) {
2108        self.get().vertex_attrib_divisor(index, divisor);
2109    }
2110    pub fn viewport(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
2111        self.get().viewport(x, y, width, height);
2112    }
2113    pub fn scissor(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
2114        self.get().scissor(x, y, width, height);
2115    }
2116    pub fn line_width(&self, width: GLfloat) {
2117        self.get().line_width(width);
2118    }
2119    pub fn use_program(&self, program: GLuint) {
2120        self.get().use_program(program);
2121    }
2122    pub fn validate_program(&self, program: GLuint) {
2123        self.get().validate_program(program);
2124    }
2125    pub fn draw_arrays(&self, mode: GLenum, first: GLint, count: GLsizei) {
2126        self.get().draw_arrays(mode, first, count);
2127    }
2128    pub fn draw_arrays_instanced(
2129        &self,
2130        mode: GLenum,
2131        first: GLint,
2132        count: GLsizei,
2133        primcount: GLsizei,
2134    ) {
2135        self.get()
2136            .draw_arrays_instanced(mode, first, count, primcount);
2137    }
2138    pub fn draw_elements(
2139        &self,
2140        mode: GLenum,
2141        count: GLsizei,
2142        element_type: GLenum,
2143        indices_offset: GLuint,
2144    ) {
2145        self.get()
2146            .draw_elements(mode, count, element_type, indices_offset);
2147    }
2148    pub fn draw_elements_instanced(
2149        &self,
2150        mode: GLenum,
2151        count: GLsizei,
2152        element_type: GLenum,
2153        indices_offset: GLuint,
2154        primcount: GLsizei,
2155    ) {
2156        self.get()
2157            .draw_elements_instanced(mode, count, element_type, indices_offset, primcount);
2158    }
2159    pub fn blend_color(&self, r: f32, g: f32, b: f32, a: f32) {
2160        self.get().blend_color(r, g, b, a);
2161    }
2162    pub fn blend_func(&self, sfactor: GLenum, dfactor: GLenum) {
2163        self.get().blend_func(sfactor, dfactor);
2164    }
2165    pub fn blend_func_separate(
2166        &self,
2167        src_rgb: GLenum,
2168        dest_rgb: GLenum,
2169        src_alpha: GLenum,
2170        dest_alpha: GLenum,
2171    ) {
2172        self.get()
2173            .blend_func_separate(src_rgb, dest_rgb, src_alpha, dest_alpha);
2174    }
2175    pub fn blend_equation(&self, mode: GLenum) {
2176        self.get().blend_equation(mode);
2177    }
2178    pub fn blend_equation_separate(&self, mode_rgb: GLenum, mode_alpha: GLenum) {
2179        self.get().blend_equation_separate(mode_rgb, mode_alpha);
2180    }
2181    // mirrors glColorMask(GLboolean, GLboolean, GLboolean, GLboolean) — the four
2182    // RGBA write-mask flags are the GL API, not a refactorable bool soup.
2183    #[allow(clippy::fn_params_excessive_bools)]
2184    pub fn color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
2185        self.get().color_mask(r, g, b, a);
2186    }
2187    pub fn cull_face(&self, mode: GLenum) {
2188        self.get().cull_face(mode);
2189    }
2190    pub fn front_face(&self, mode: GLenum) {
2191        self.get().front_face(mode);
2192    }
2193    pub fn enable(&self, cap: GLenum) {
2194        self.get().enable(cap);
2195    }
2196    pub fn disable(&self, cap: GLenum) {
2197        self.get().disable(cap);
2198    }
2199    pub fn hint(&self, param_name: GLenum, param_val: GLenum) {
2200        self.get().hint(param_name, param_val);
2201    }
2202    #[must_use] pub fn is_enabled(&self, cap: GLenum) -> GLboolean {
2203        self.get().is_enabled(cap)
2204    }
2205    #[must_use] pub fn is_shader(&self, shader: GLuint) -> GLboolean {
2206        self.get().is_shader(shader)
2207    }
2208    #[must_use] pub fn is_texture(&self, texture: GLenum) -> GLboolean {
2209        self.get().is_texture(texture)
2210    }
2211    #[must_use] pub fn is_framebuffer(&self, framebuffer: GLenum) -> GLboolean {
2212        self.get().is_framebuffer(framebuffer)
2213    }
2214    #[must_use] pub fn is_renderbuffer(&self, renderbuffer: GLenum) -> GLboolean {
2215        self.get().is_renderbuffer(renderbuffer)
2216    }
2217    #[must_use] pub fn check_frame_buffer_status(&self, target: GLenum) -> GLenum {
2218        self.get().check_frame_buffer_status(target)
2219    }
2220    pub fn enable_vertex_attrib_array(&self, index: GLuint) {
2221        self.get().enable_vertex_attrib_array(index);
2222    }
2223    pub fn disable_vertex_attrib_array(&self, index: GLuint) {
2224        self.get().disable_vertex_attrib_array(index);
2225    }
2226    pub fn uniform_1f(&self, location: GLint, v0: GLfloat) {
2227        self.get().uniform_1f(location, v0);
2228    }
2229    pub fn uniform_1fv(&self, location: GLint, values: F32VecRef) {
2230        self.get().uniform_1fv(location, values.as_slice());
2231    }
2232    pub fn uniform_1i(&self, location: GLint, v0: GLint) {
2233        self.get().uniform_1i(location, v0);
2234    }
2235    pub fn uniform_1iv(&self, location: GLint, values: I32VecRef) {
2236        self.get().uniform_1iv(location, values.as_slice());
2237    }
2238    pub fn uniform_1ui(&self, location: GLint, v0: GLuint) {
2239        self.get().uniform_1ui(location, v0);
2240    }
2241    pub fn uniform_2f(&self, location: GLint, v0: GLfloat, v1: GLfloat) {
2242        self.get().uniform_2f(location, v0, v1);
2243    }
2244    pub fn uniform_2fv(&self, location: GLint, values: F32VecRef) {
2245        self.get().uniform_2fv(location, values.as_slice());
2246    }
2247    pub fn uniform_2i(&self, location: GLint, v0: GLint, v1: GLint) {
2248        self.get().uniform_2i(location, v0, v1);
2249    }
2250    pub fn uniform_2iv(&self, location: GLint, values: I32VecRef) {
2251        self.get().uniform_2iv(location, values.as_slice());
2252    }
2253    pub fn uniform_2ui(&self, location: GLint, v0: GLuint, v1: GLuint) {
2254        self.get().uniform_2ui(location, v0, v1);
2255    }
2256    pub fn uniform_3f(&self, location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat) {
2257        self.get().uniform_3f(location, v0, v1, v2);
2258    }
2259    pub fn uniform_3fv(&self, location: GLint, values: F32VecRef) {
2260        self.get().uniform_3fv(location, values.as_slice());
2261    }
2262    pub fn uniform_3i(&self, location: GLint, v0: GLint, v1: GLint, v2: GLint) {
2263        self.get().uniform_3i(location, v0, v1, v2);
2264    }
2265    pub fn uniform_3iv(&self, location: GLint, values: I32VecRef) {
2266        self.get().uniform_3iv(location, values.as_slice());
2267    }
2268    pub fn uniform_3ui(&self, location: GLint, v0: GLuint, v1: GLuint, v2: GLuint) {
2269        self.get().uniform_3ui(location, v0, v1, v2);
2270    }
2271    pub fn uniform_4f(&self, location: GLint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
2272        self.get().uniform_4f(location, x, y, z, w);
2273    }
2274    pub fn uniform_4i(&self, location: GLint, x: GLint, y: GLint, z: GLint, w: GLint) {
2275        self.get().uniform_4i(location, x, y, z, w);
2276    }
2277    pub fn uniform_4iv(&self, location: GLint, values: I32VecRef) {
2278        self.get().uniform_4iv(location, values.as_slice());
2279    }
2280    pub fn uniform_4ui(&self, location: GLint, x: GLuint, y: GLuint, z: GLuint, w: GLuint) {
2281        self.get().uniform_4ui(location, x, y, z, w);
2282    }
2283    pub fn uniform_4fv(&self, location: GLint, values: F32VecRef) {
2284        self.get().uniform_4fv(location, values.as_slice());
2285    }
2286    pub fn uniform_matrix_2fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2287        self.get()
2288            .uniform_matrix_2fv(location, transpose, value.as_slice());
2289    }
2290    pub fn uniform_matrix_3fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2291        self.get()
2292            .uniform_matrix_3fv(location, transpose, value.as_slice());
2293    }
2294    pub fn uniform_matrix_4fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2295        self.get()
2296            .uniform_matrix_4fv(location, transpose, value.as_slice());
2297    }
2298    pub fn depth_mask(&self, flag: bool) {
2299        self.get().depth_mask(flag);
2300    }
2301    pub fn depth_range(&self, near: f64, far: f64) {
2302        self.get().depth_range(near, far);
2303    }
2304    #[must_use] pub fn get_active_attrib(&self, program: GLuint, index: GLuint) -> GetActiveAttribReturn {
2305        let r = self.get().get_active_attrib(program, index);
2306        GetActiveAttribReturn {
2307            _0: r.0,
2308            _1: r.1,
2309            _2: r.2.into(),
2310        }
2311    }
2312    #[must_use] pub fn get_active_uniform(&self, program: GLuint, index: GLuint) -> GetActiveUniformReturn {
2313        let r = self.get().get_active_uniform(program, index);
2314        GetActiveUniformReturn {
2315            _0: r.0,
2316            _1: r.1,
2317            _2: r.2.into(),
2318        }
2319    }
2320    #[must_use] pub fn get_active_uniforms_iv(
2321        &self,
2322        program: GLuint,
2323        indices: GLuintVec,
2324        pname: GLenum,
2325    ) -> GLintVec {
2326        self.get()
2327            .get_active_uniforms_iv(program, indices.into_library_owned_vec(), pname)
2328            .into()
2329    }
2330    #[must_use] pub fn get_active_uniform_block_i(
2331        &self,
2332        program: GLuint,
2333        index: GLuint,
2334        pname: GLenum,
2335    ) -> GLint {
2336        self.get().get_active_uniform_block_i(program, index, pname)
2337    }
2338    #[must_use] pub fn get_active_uniform_block_iv(
2339        &self,
2340        program: GLuint,
2341        index: GLuint,
2342        pname: GLenum,
2343    ) -> GLintVec {
2344        self.get()
2345            .get_active_uniform_block_iv(program, index, pname)
2346            .into()
2347    }
2348    #[must_use] pub fn get_active_uniform_block_name(&self, program: GLuint, index: GLuint) -> AzString {
2349        self.get()
2350            .get_active_uniform_block_name(program, index)
2351            .into()
2352    }
2353    #[must_use] pub fn get_attrib_location(&self, program: GLuint, name: &str) -> c_int {
2354        self.get().get_attrib_location(program, name)
2355    }
2356    #[must_use] pub fn get_frag_data_location(&self, program: GLuint, name: &str) -> c_int {
2357        self.get().get_frag_data_location(program, name)
2358    }
2359    #[must_use] pub fn get_uniform_location(&self, program: GLuint, name: &str) -> c_int {
2360        self.get().get_uniform_location(program, name)
2361    }
2362    #[must_use] pub fn get_program_info_log(&self, program: GLuint) -> AzString {
2363        self.get().get_program_info_log(program).into()
2364    }
2365    pub fn get_program_iv(&self, program: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2366        unsafe {
2367            self.get()
2368                .get_program_iv(program, pname, result.as_mut_slice());
2369        }
2370    }
2371    #[must_use] pub fn get_program_binary(&self, program: GLuint) -> GetProgramBinaryReturn {
2372        let r = self.get().get_program_binary(program);
2373        GetProgramBinaryReturn {
2374            _0: r.0.into(),
2375            _1: r.1,
2376        }
2377    }
2378    pub fn program_binary(&self, program: GLuint, format: GLenum, binary: U8VecRef) {
2379        self.get()
2380            .program_binary(program, format, binary.as_slice());
2381    }
2382    pub fn program_parameter_i(&self, program: GLuint, pname: GLenum, value: GLint) {
2383        self.get().program_parameter_i(program, pname, value);
2384    }
2385    pub fn get_vertex_attrib_iv(&self, index: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2386        unsafe {
2387            self.get()
2388                .get_vertex_attrib_iv(index, pname, result.as_mut_slice());
2389        }
2390    }
2391    pub fn get_vertex_attrib_fv(&self, index: GLuint, pname: GLenum, mut result: GLfloatVecRefMut) {
2392        unsafe {
2393            self.get()
2394                .get_vertex_attrib_fv(index, pname, result.as_mut_slice());
2395        }
2396    }
2397    #[must_use] pub fn get_vertex_attrib_pointer_v(&self, index: GLuint, pname: GLenum) -> GLsizeiptr {
2398        self.get().get_vertex_attrib_pointer_v(index, pname)
2399    }
2400    #[must_use] pub fn get_buffer_parameter_iv(&self, target: GLuint, pname: GLenum) -> GLint {
2401        self.get().get_buffer_parameter_iv(target, pname)
2402    }
2403    #[must_use] pub fn get_shader_info_log(&self, shader: GLuint) -> AzString {
2404        self.get().get_shader_info_log(shader).into()
2405    }
2406    #[must_use] pub fn get_string(&self, which: GLenum) -> AzString {
2407        self.get().get_string(which).into()
2408    }
2409    #[must_use] pub fn get_string_i(&self, which: GLenum, index: GLuint) -> AzString {
2410        self.get().get_string_i(which, index).into()
2411    }
2412    pub fn get_shader_iv(&self, shader: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2413        unsafe {
2414            self.get()
2415                .get_shader_iv(shader, pname, result.as_mut_slice());
2416        }
2417    }
2418    #[must_use] pub fn get_shader_precision_format(
2419        &self,
2420        shader_type: GLuint,
2421        precision_type: GLuint,
2422    ) -> GlShaderPrecisionFormatReturn {
2423        let r = self
2424            .get()
2425            .get_shader_precision_format(shader_type, precision_type);
2426        GlShaderPrecisionFormatReturn {
2427            _0: r.0,
2428            _1: r.1,
2429            _2: r.2,
2430        }
2431    }
2432    pub fn compile_shader(&self, shader: GLuint) {
2433        self.get().compile_shader(shader);
2434    }
2435    #[must_use] pub fn create_program(&self) -> GLuint {
2436        self.get().create_program()
2437    }
2438    pub fn delete_program(&self, program: GLuint) {
2439        self.get().delete_program(program);
2440    }
2441    #[must_use] pub fn create_shader(&self, shader_type: GLenum) -> GLuint {
2442        self.get().create_shader(shader_type)
2443    }
2444    pub fn delete_shader(&self, shader: GLuint) {
2445        self.get().delete_shader(shader);
2446    }
2447    pub fn detach_shader(&self, program: GLuint, shader: GLuint) {
2448        self.get().detach_shader(program, shader);
2449    }
2450    pub fn link_program(&self, program: GLuint) {
2451        self.get().link_program(program);
2452    }
2453    pub fn clear_color(&self, r: f32, g: f32, b: f32, a: f32) {
2454        self.get().clear_color(r, g, b, a);
2455    }
2456    pub fn clear(&self, buffer_mask: GLbitfield) {
2457        self.get().clear(buffer_mask);
2458    }
2459    pub fn clear_depth(&self, depth: f64) {
2460        self.get().clear_depth(depth);
2461    }
2462    pub fn clear_stencil(&self, s: GLint) {
2463        self.get().clear_stencil(s);
2464    }
2465    pub fn flush(&self) {
2466        self.get().flush();
2467    }
2468    pub fn finish(&self) {
2469        self.get().finish();
2470    }
2471    #[must_use] pub fn get_error(&self) -> GLenum {
2472        self.get().get_error()
2473    }
2474    pub fn stencil_mask(&self, mask: GLuint) {
2475        self.get().stencil_mask(mask);
2476    }
2477    pub fn stencil_mask_separate(&self, face: GLenum, mask: GLuint) {
2478        self.get().stencil_mask_separate(face, mask);
2479    }
2480    pub fn stencil_func(&self, func: GLenum, ref_: GLint, mask: GLuint) {
2481        self.get().stencil_func(func, ref_, mask);
2482    }
2483    pub fn stencil_func_separate(&self, face: GLenum, func: GLenum, ref_: GLint, mask: GLuint) {
2484        self.get().stencil_func_separate(face, func, ref_, mask);
2485    }
2486    pub fn stencil_op(&self, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
2487        self.get().stencil_op(sfail, dpfail, dppass);
2488    }
2489    pub fn stencil_op_separate(&self, face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
2490        self.get().stencil_op_separate(face, sfail, dpfail, dppass);
2491    }
2492    pub fn egl_image_target_texture2d_oes(&self, target: GLenum, image: GlVoidPtrConst) {
2493        self.get()
2494            .egl_image_target_texture2d_oes(target, image.ptr as *const c_void);
2495    }
2496    pub fn generate_mipmap(&self, target: GLenum) {
2497        self.get().generate_mipmap(target);
2498    }
2499    pub fn insert_event_marker_ext(&self, message: &str) {
2500        self.get().insert_event_marker_ext(message);
2501    }
2502    pub fn push_group_marker_ext(&self, message: &str) {
2503        self.get().push_group_marker_ext(message);
2504    }
2505    pub fn pop_group_marker_ext(&self) {
2506        self.get().pop_group_marker_ext();
2507    }
2508    pub fn debug_message_insert_khr(
2509        &self,
2510        source: GLenum,
2511        type_: GLenum,
2512        id: GLuint,
2513        severity: GLenum,
2514        message: &str,
2515    ) {
2516        self.get()
2517            .debug_message_insert_khr(source, type_, id, severity, message);
2518    }
2519    pub fn push_debug_group_khr(&self, source: GLenum, id: GLuint, message: &str) {
2520        self.get()
2521            .push_debug_group_khr(source, id, message);
2522    }
2523    pub fn pop_debug_group_khr(&self) {
2524        self.get().pop_debug_group_khr();
2525    }
2526    #[must_use] pub fn fence_sync(&self, condition: GLenum, flags: GLbitfield) -> GLsyncPtr {
2527        GLsyncPtr::new(self.get().fence_sync(condition, flags))
2528    }
2529    #[must_use] pub fn client_wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) -> u32 {
2530        self.get().client_wait_sync(sync.get(), flags, timeout)
2531    }
2532    pub fn wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) {
2533        self.get().wait_sync(sync.get(), flags, timeout);
2534    }
2535    pub fn delete_sync(&self, sync: GLsyncPtr) {
2536        self.get().delete_sync(sync.get());
2537    }
2538    pub fn texture_range_apple(&self, target: GLenum, data: U8VecRef) {
2539        self.get().texture_range_apple(target, data.as_slice());
2540    }
2541    #[must_use] pub fn gen_fences_apple(&self, n: GLsizei) -> GLuintVec {
2542        self.get().gen_fences_apple(n).into()
2543    }
2544    pub fn delete_fences_apple(&self, fences: GLuintVecRef) {
2545        self.get().delete_fences_apple(fences.as_slice());
2546    }
2547    pub fn set_fence_apple(&self, fence: GLuint) {
2548        self.get().set_fence_apple(fence);
2549    }
2550    pub fn finish_fence_apple(&self, fence: GLuint) {
2551        self.get().finish_fence_apple(fence);
2552    }
2553    pub fn test_fence_apple(&self, fence: GLuint) {
2554        self.get().test_fence_apple(fence);
2555    }
2556    #[must_use] pub fn test_object_apple(&self, object: GLenum, name: GLuint) -> GLboolean {
2557        self.get().test_object_apple(object, name)
2558    }
2559    pub fn finish_object_apple(&self, object: GLenum, name: GLuint) {
2560        self.get().finish_object_apple(object, name);
2561    }
2562    #[must_use] pub fn get_frag_data_index(&self, program: GLuint, name: &str) -> GLint {
2563        self.get().get_frag_data_index(program, name)
2564    }
2565    pub fn blend_barrier_khr(&self) {
2566        self.get().blend_barrier_khr();
2567    }
2568    pub fn bind_frag_data_location_indexed(
2569        &self,
2570        program: GLuint,
2571        color_number: GLuint,
2572        index: GLuint,
2573        name: &str,
2574    ) {
2575        self.get()
2576            .bind_frag_data_location_indexed(program, color_number, index, name);
2577    }
2578    #[must_use] pub fn get_debug_messages(&self) -> DebugMessageVec {
2579        let dmv: Vec<DebugMessage> = self
2580            .get()
2581            .get_debug_messages()
2582            .into_iter()
2583            .map(|d| DebugMessage {
2584                message: d.message.into(),
2585                source: d.source,
2586                ty: d.ty,
2587                id: d.id,
2588                severity: d.severity,
2589            })
2590            .collect();
2591        dmv.into()
2592    }
2593    pub fn provoking_vertex_angle(&self, mode: GLenum) {
2594        self.get().provoking_vertex_angle(mode);
2595    }
2596    #[must_use] pub fn gen_vertex_arrays_apple(&self, n: GLsizei) -> GLuintVec {
2597        self.get().gen_vertex_arrays_apple(n).into()
2598    }
2599    pub fn bind_vertex_array_apple(&self, vao: GLuint) {
2600        self.get().bind_vertex_array_apple(vao);
2601    }
2602    pub fn delete_vertex_arrays_apple(&self, vertex_arrays: GLuintVecRef) {
2603        self.get()
2604            .delete_vertex_arrays_apple(vertex_arrays.as_slice());
2605    }
2606    pub fn copy_texture_chromium(
2607        &self,
2608        source_id: GLuint,
2609        source_level: GLint,
2610        dest_target: GLenum,
2611        dest_id: GLuint,
2612        dest_level: GLint,
2613        internal_format: GLint,
2614        dest_type: GLenum,
2615        unpack_flip_y: GLboolean,
2616        unpack_premultiply_alpha: GLboolean,
2617        unpack_unmultiply_alpha: GLboolean,
2618    ) {
2619        self.get().copy_texture_chromium(
2620            source_id,
2621            source_level,
2622            dest_target,
2623            dest_id,
2624            dest_level,
2625            internal_format,
2626            dest_type,
2627            unpack_flip_y,
2628            unpack_premultiply_alpha,
2629            unpack_unmultiply_alpha,
2630        );
2631    }
2632    pub fn copy_sub_texture_chromium(
2633        &self,
2634        source_id: GLuint,
2635        source_level: GLint,
2636        dest_target: GLenum,
2637        dest_id: GLuint,
2638        dest_level: GLint,
2639        x_offset: GLint,
2640        y_offset: GLint,
2641        x: GLint,
2642        y: GLint,
2643        width: GLsizei,
2644        height: GLsizei,
2645        unpack_flip_y: GLboolean,
2646        unpack_premultiply_alpha: GLboolean,
2647        unpack_unmultiply_alpha: GLboolean,
2648    ) {
2649        self.get().copy_sub_texture_chromium(
2650            source_id,
2651            source_level,
2652            dest_target,
2653            dest_id,
2654            dest_level,
2655            x_offset,
2656            y_offset,
2657            x,
2658            y,
2659            width,
2660            height,
2661            unpack_flip_y,
2662            unpack_premultiply_alpha,
2663            unpack_unmultiply_alpha,
2664        );
2665    }
2666    pub fn egl_image_target_renderbuffer_storage_oes(&self, target: u32, image: GlVoidPtrConst) {
2667        self.get().egl_image_target_renderbuffer_storage_oes(
2668            target,
2669            image.ptr as *const c_void,
2670        );
2671    }
2672    pub fn copy_texture_3d_angle(
2673        &self,
2674        source_id: GLuint,
2675        source_level: GLint,
2676        dest_target: GLenum,
2677        dest_id: GLuint,
2678        dest_level: GLint,
2679        internal_format: GLint,
2680        dest_type: GLenum,
2681        unpack_flip_y: GLboolean,
2682        unpack_premultiply_alpha: GLboolean,
2683        unpack_unmultiply_alpha: GLboolean,
2684    ) {
2685        self.get().copy_texture_3d_angle(
2686            source_id,
2687            source_level,
2688            dest_target,
2689            dest_id,
2690            dest_level,
2691            internal_format,
2692            dest_type,
2693            unpack_flip_y,
2694            unpack_premultiply_alpha,
2695            unpack_unmultiply_alpha,
2696        );
2697    }
2698    pub fn copy_sub_texture_3d_angle(
2699        &self,
2700        source_id: GLuint,
2701        source_level: GLint,
2702        dest_target: GLenum,
2703        dest_id: GLuint,
2704        dest_level: GLint,
2705        x_offset: GLint,
2706        y_offset: GLint,
2707        z_offset: GLint,
2708        x: GLint,
2709        y: GLint,
2710        z: GLint,
2711        width: GLsizei,
2712        height: GLsizei,
2713        depth: GLsizei,
2714        unpack_flip_y: GLboolean,
2715        unpack_premultiply_alpha: GLboolean,
2716        unpack_unmultiply_alpha: GLboolean,
2717    ) {
2718        self.get().copy_sub_texture_3d_angle(
2719            source_id,
2720            source_level,
2721            dest_target,
2722            dest_id,
2723            dest_level,
2724            x_offset,
2725            y_offset,
2726            z_offset,
2727            x,
2728            y,
2729            z,
2730            width,
2731            height,
2732            depth,
2733            unpack_flip_y,
2734            unpack_premultiply_alpha,
2735            unpack_unmultiply_alpha,
2736        );
2737    }
2738    pub fn buffer_storage(
2739        &self,
2740        target: GLenum,
2741        size: GLsizeiptr,
2742        data: GlVoidPtrConst,
2743        flags: GLbitfield,
2744    ) {
2745        self.get().buffer_storage(target, size, data.ptr, flags);
2746    }
2747    pub fn flush_mapped_buffer_range(&self, target: GLenum, offset: GLintptr, length: GLsizeiptr) {
2748        self.get().flush_mapped_buffer_range(target, offset, length);
2749    }
2750}
2751
2752impl PartialEq for GlContextPtr {
2753    fn eq(&self, rhs: &Self) -> bool {
2754        self.as_usize().eq(&rhs.as_usize())
2755    }
2756}
2757
2758impl Eq for GlContextPtr {}
2759
2760impl PartialOrd for GlContextPtr {
2761    fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
2762        self.as_usize().partial_cmp(&rhs.as_usize())
2763    }
2764}
2765
2766impl Ord for GlContextPtr {
2767    fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
2768        self.as_usize().cmp(&rhs.as_usize())
2769    }
2770}
2771
2772/// Saved OpenGL state for save/restore around framebuffer operations.
2773/// Used by `Texture::clear()` and `GlShader::draw()` to avoid corrupting
2774/// the caller's GL state.
2775// the `current_` prefix is intentional: each field holds the saved CURRENT GL
2776// binding captured at save() to be restored in restore().
2777#[allow(clippy::struct_field_names)]
2778struct GlStateSave {
2779    current_multisample: [u8; 1],
2780    current_index_buffer: [i32; 1],
2781    current_vertex_buffer: [i32; 1],
2782    current_vertex_array_object: [i32; 1],
2783    current_program: [i32; 1],
2784    current_framebuffers: [i32; 1],
2785    current_renderbuffers: [i32; 1],
2786    current_texture_2d: [i32; 1],
2787}
2788
2789impl GlStateSave {
2790    fn save(gl_context: &GlContextPtr) -> Self {
2791        let mut s = Self {
2792            current_multisample: [0],
2793            current_index_buffer: [0],
2794            current_vertex_buffer: [0],
2795            current_vertex_array_object: [0],
2796            current_program: [0],
2797            current_framebuffers: [0],
2798            current_renderbuffers: [0],
2799            current_texture_2d: [0],
2800        };
2801
2802        gl_context.get_boolean_v(gl::MULTISAMPLE, (&mut s.current_multisample[..]).into());
2803        gl_context.get_integer_v(gl::ARRAY_BUFFER_BINDING, (&mut s.current_vertex_buffer[..]).into());
2804        gl_context.get_integer_v(gl::ELEMENT_ARRAY_BUFFER_BINDING, (&mut s.current_index_buffer[..]).into());
2805        gl_context.get_integer_v(gl::CURRENT_PROGRAM, (&mut s.current_program[..]).into());
2806        gl_context.get_integer_v(gl::VERTEX_ARRAY_BINDING, (&mut s.current_vertex_array_object[..]).into());
2807        gl_context.get_integer_v(gl::RENDERBUFFER, (&mut s.current_renderbuffers[..]).into());
2808        gl_context.get_integer_v(gl::FRAMEBUFFER, (&mut s.current_framebuffers[..]).into());
2809        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut s.current_texture_2d[..]).into());
2810
2811        s
2812    }
2813
2814    // OpenGL binding: state values passed to the gl API as GLuint/GLsizei.
2815    #[allow(clippy::cast_sign_loss)]
2816    fn restore(&self, gl_context: &GlContextPtr) {
2817        if u32::from(self.current_multisample[0]) == gl::TRUE {
2818            gl_context.enable(gl::MULTISAMPLE);
2819        }
2820        gl_context.bind_framebuffer(gl::FRAMEBUFFER, self.current_framebuffers[0] as u32);
2821        gl_context.bind_texture(gl::TEXTURE_2D, self.current_texture_2d[0] as u32);
2822        gl_context.bind_buffer(gl::RENDERBUFFER, self.current_renderbuffers[0] as u32);
2823        gl_context.bind_vertex_array(self.current_vertex_array_object[0] as u32);
2824        gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.current_index_buffer[0] as u32);
2825        gl_context.bind_buffer(gl::ARRAY_BUFFER, self.current_vertex_buffer[0] as u32);
2826        gl_context.use_program(self.current_program[0] as u32);
2827    }
2828}
2829
2830/// AUDIT: RAII guard that deletes a transient framebuffer + renderbuffer on
2831/// scope exit. Used by `Texture::clear` and `GlShader::draw` so that a panic
2832/// mid-path (e.g. an `.unwrap()` on an empty gen-list, or any GL step that
2833/// panics) can't leak the FBO/RBO. Ids of `0` are skipped (GL treats delete-0
2834/// as a no-op anyway, but this keeps intent explicit).
2835struct FboRboGuard<'a> {
2836    gl_context: &'a GlContextPtr,
2837    framebuffer_id: GLuint,
2838    renderbuffer_id: GLuint,
2839}
2840
2841impl Drop for FboRboGuard<'_> {
2842    fn drop(&mut self) {
2843        if self.framebuffer_id != 0 {
2844            self.gl_context
2845                .delete_framebuffers((&[self.framebuffer_id])[..].into());
2846        }
2847        if self.renderbuffer_id != 0 {
2848            self.gl_context
2849                .delete_renderbuffers((&[self.renderbuffer_id])[..].into());
2850        }
2851    }
2852}
2853
2854/// OpenGL texture, use `ReadOnlyWindow::create_texture` to create a texture
2855#[repr(C)]
2856pub struct Texture {
2857    /// A reference-counted pointer to the OpenGL context (so that the texture can be deleted in
2858    /// the destructor)
2859    pub gl_context: GlContextPtr,
2860    /// Raw OpenGL texture ID
2861    pub texture_id: GLuint,
2862    /// Reference count, shared across
2863    pub refcount: *const AtomicUsize,
2864    /// Size of this texture (in pixels)
2865    pub size: PhysicalSizeU32,
2866    /// Format of the texture (rgba8, brga8, etc.)
2867    pub format: RawImageFormat,
2868    /// Background color of this texture
2869    pub background_color: ColorU,
2870    /// Hints and flags for optimization purposes
2871    pub flags: TextureFlags,
2872    pub run_destructor: bool,
2873}
2874
2875impl Clone for Texture {
2876    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
2877    fn clone(&self) -> Self {
2878        unsafe {
2879            (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst);
2880        }
2881        Self {
2882            gl_context: self.gl_context.clone(),
2883            texture_id: self.texture_id,
2884            refcount: self.refcount,
2885            size: self.size,
2886            format: self.format,
2887            background_color: self.background_color,
2888            flags: self.flags,
2889            run_destructor: true,
2890        }
2891    }
2892}
2893
2894impl_option!(
2895    Texture,
2896    OptionTexture,
2897    copy = false,
2898    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2899);
2900
2901impl Texture {
2902    #[must_use] pub fn create(
2903        texture_id: GLuint,
2904        flags: TextureFlags,
2905        size: PhysicalSizeU32,
2906        background_color: ColorU,
2907        gl_context: GlContextPtr,
2908        format: RawImageFormat,
2909    ) -> Self {
2910        Self {
2911            texture_id,
2912            flags,
2913            size,
2914            background_color,
2915            gl_context,
2916            format,
2917            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
2918            run_destructor: true,
2919        }
2920    }
2921
2922    // OpenGL binding: gl::* enum constants and texture dimensions are passed as
2923    // GLint/GLsizei (i32); the values are GL-bounded and the `as i32` casts are the
2924    // idiomatic form for the gl API.
2925    #[allow(clippy::cast_possible_wrap)]
2926    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
2927    #[must_use] pub fn allocate_rgba8(
2928        gl_context: GlContextPtr,
2929        size: PhysicalSizeU32,
2930        background: ColorU,
2931    ) -> Self {
2932        let textures = gl_context.gen_textures(1);
2933        let texture_id = textures.as_ref()[0];
2934
2935        let mut current_texture_2d = [0_i32];
2936        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut current_texture_2d[..]).into());
2937
2938        gl_context.bind_texture(gl::TEXTURE_2D, texture_id);
2939        gl_context.tex_image_2d(
2940            gl::TEXTURE_2D,
2941            0,
2942            gl::RGBA as i32,
2943            size.width as i32,
2944            size.height as i32,
2945            0,
2946            gl::RGBA,
2947            gl::UNSIGNED_BYTE,
2948            None.into(),
2949        );
2950        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
2951        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
2952        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
2953        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
2954        gl_context.bind_texture(gl::TEXTURE_2D, current_texture_2d[0] as u32);
2955
2956        Self::create(
2957            texture_id,
2958            TextureFlags {
2959                is_opaque: false,
2960                is_video_texture: false,
2961            },
2962            size,
2963            background,
2964            gl_context,
2965            // Format is BGRA8 for WebRender integration, despite the GL upload using RGBA
2966            RawImageFormat::BGRA8,
2967        )
2968    }
2969
2970    /// # Panics
2971    ///
2972    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
2973    // OpenGL binding: gl::* enum constants and texture dimensions passed as
2974    // GLint/GLsizei (i32); values are GL-bounded, `as i32` is the idiomatic form.
2975    #[allow(clippy::cast_possible_wrap)]
2976    pub fn clear(&mut self) {
2977        let saved = GlStateSave::save(&self.gl_context);
2978
2979        let framebuffers = self.gl_context.gen_framebuffers(1);
2980        let framebuffer_id = *framebuffers.get(0).unwrap();
2981        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
2982        // panic in `gen_renderbuffers().get(0).unwrap()` can't leak it.
2983        let mut fbo_rbo_guard = FboRboGuard {
2984            gl_context: &self.gl_context,
2985            framebuffer_id,
2986            renderbuffer_id: 0,
2987        };
2988        self.gl_context
2989            .bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
2990
2991        let depthbuffers = self.gl_context.gen_renderbuffers(1);
2992        let depthbuffer_id = *depthbuffers.get(0).unwrap();
2993        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
2994
2995        self.gl_context
2996            .bind_texture(gl::TEXTURE_2D, self.texture_id);
2997        self.gl_context.tex_image_2d(
2998            gl::TEXTURE_2D,
2999            0,
3000            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
3001            self.size.width as i32,
3002            self.size.height as i32,
3003            0,
3004            gl::RGBA, // gl::BGRA?
3005            gl::UNSIGNED_BYTE,
3006            None.into(),
3007        );
3008        self.gl_context
3009            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
3010        self.gl_context
3011            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
3012        self.gl_context.tex_parameter_i(
3013            gl::TEXTURE_2D,
3014            gl::TEXTURE_WRAP_S,
3015            gl::CLAMP_TO_EDGE as i32,
3016        );
3017        self.gl_context.tex_parameter_i(
3018            gl::TEXTURE_2D,
3019            gl::TEXTURE_WRAP_T,
3020            gl::CLAMP_TO_EDGE as i32,
3021        );
3022
3023        self.gl_context
3024            .bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
3025        self.gl_context.renderbuffer_storage(
3026            gl::RENDERBUFFER,
3027            gl::DEPTH_COMPONENT,
3028            self.size.width as i32,
3029            self.size.height as i32,
3030        );
3031        self.gl_context.framebuffer_renderbuffer(
3032            gl::FRAMEBUFFER,
3033            gl::DEPTH_ATTACHMENT,
3034            gl::RENDERBUFFER,
3035            depthbuffer_id,
3036        );
3037
3038        self.gl_context.framebuffer_texture_2d(
3039            gl::FRAMEBUFFER,
3040            gl::COLOR_ATTACHMENT0,
3041            gl::TEXTURE_2D,
3042            self.texture_id,
3043            0,
3044        );
3045        self.gl_context
3046            .draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
3047
3048        let clear_color: ColorF = self.background_color.into();
3049        self.gl_context
3050            .clear_color(clear_color.r, clear_color.g, clear_color.b, clear_color.a);
3051        self.gl_context.clear_depth(0.0);
3052        self.gl_context
3053            .clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
3054
3055        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
3056        // also runs on an unwinding panic), so we restore state and let the
3057        // guard reclaim the GL objects at scope exit.
3058        saved.restore(&self.gl_context);
3059        drop(fbo_rbo_guard);
3060    }
3061
3062    #[must_use] pub fn get_descriptor(&self) -> ImageDescriptor {
3063        ImageDescriptor {
3064            format: self.format,
3065            width: self.size.width as usize,
3066            height: self.size.height as usize,
3067            stride: None.into(),
3068            offset: 0,
3069            flags: ImageDescriptorFlags {
3070                is_opaque: self.flags.is_opaque,
3071                // The texture gets mapped 1:1 onto the display, so there is no need for mipmaps
3072                allow_mipmaps: false,
3073            },
3074        }
3075    }
3076
3077    /// Draws a `TessellatedGPUSvgNode` with the given color to the texture
3078    pub fn draw_tesselated_svg_gpu_node(
3079        &mut self,
3080        node: &TessellatedGPUSvgNode,
3081        size: PhysicalSizeU32,
3082        color: ColorU,
3083        transforms: StyleTransformVec,
3084    ) -> bool {
3085        node.draw(self, size, color, transforms)
3086    }
3087
3088    /// Draws a `TessellatedColoredGPUSvgNode` to the texture
3089    pub fn draw_tesselated_colored_svg_gpu_node(
3090        &mut self,
3091        node: &crate::svg::TessellatedColoredGPUSvgNode,
3092        size: PhysicalSizeU32,
3093        transforms: StyleTransformVec,
3094    ) -> bool {
3095        node.draw(self, size, transforms)
3096    }
3097}
3098
3099#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3100#[repr(C)]
3101pub struct TextureFlags {
3102    /// Whether this texture contains an alpha component
3103    pub is_opaque: bool,
3104    /// Optimization: use the compositor instead of OpenGL for energy optimization
3105    pub is_video_texture: bool,
3106}
3107
3108impl ::core::fmt::Display for Texture {
3109    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3110        write!(
3111            f,
3112            "Texture {{ id: {}, {}x{} }}",
3113            self.texture_id, self.size.width, self.size.height
3114        )
3115    }
3116}
3117
3118macro_rules! impl_traits_for_gl_object {
3119    ($struct_name:ident, $gl_id_field:ident) => {
3120        impl ::core::fmt::Debug for $struct_name {
3121            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3122                write!(f, "{}", self)
3123            }
3124        }
3125
3126        impl Hash for $struct_name {
3127            fn hash<H: Hasher>(&self, state: &mut H) {
3128                self.$gl_id_field.hash(state);
3129            }
3130        }
3131
3132        impl PartialEq for $struct_name {
3133            fn eq(&self, other: &$struct_name) -> bool {
3134                self.$gl_id_field == other.$gl_id_field
3135            }
3136        }
3137
3138        impl Eq for $struct_name {}
3139
3140        impl PartialOrd for $struct_name {
3141            fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
3142                Some((self.$gl_id_field).cmp(&(other.$gl_id_field)))
3143            }
3144        }
3145
3146        impl Ord for $struct_name {
3147            fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
3148                (self.$gl_id_field).cmp(&(other.$gl_id_field))
3149            }
3150        }
3151    };
3152}
3153
3154impl Texture {
3155    /// GPU painting: stamp one soft-brush dab centered at (`cx`, `cy`) in texture
3156    /// pixel coordinates (origin top-left, matching [`RawImage::paint_dot`]).
3157    /// No-op if the GL context is unusable -- the caller should then use the CPU
3158    /// `RawImage` path (`GlContextPtr::is_gl_usable`).
3159    pub fn paint_dot(&mut self, cx: f32, cy: f32, brush: Brush) {
3160        self.paint_stroke(cx, cy, cx, cy, brush);
3161    }
3162
3163    /// GPU painting: stamp dabs along (`x0`,`y0`)->(`x1`,`y1`) into this texture
3164    /// via an FBO + the soft-brush shader, alpha-over blended. Same spacing +
3165    /// falloff as the CPU `RawImage::paint_stroke`. No-op if GL is unusable.
3166    #[allow(clippy::suboptimal_flops)] // mul_add not guaranteed faster/available without target +fma; keep explicit a*b+c
3167    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3168    #[allow(clippy::many_single_char_names)] // domain-standard colour/coordinate component names
3169    pub fn paint_stroke(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, brush: Brush) {
3170        let gl = self.gl_context.clone();
3171        let prog = gl.get_brush_shader();
3172        let (tw, th) = (self.size.width as f32, self.size.height as f32);
3173        // `!(radius > 0.0)` intentionally also rejects NaN (`radius <= 0.0` would not).
3174        #[allow(clippy::neg_cmp_op_on_partial_ord)]
3175        if prog == 0 || self.texture_id == 0 || !(brush.radius > 0.0) || tw <= 0.0 || th <= 0.0 {
3176            return;
3177        }
3178
3179        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
3180        let vbo = gl.gen_buffers(1).get(0).copied().unwrap_or(0);
3181        if fbo == 0 || vbo == 0 {
3182            if fbo != 0 {
3183                gl.delete_framebuffers((&[fbo][..]).into());
3184            }
3185            if vbo != 0 {
3186                gl.delete_buffers((&[vbo][..]).into());
3187            }
3188            return;
3189        }
3190
3191        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
3192        gl.framebuffer_texture_2d(
3193            gl::FRAMEBUFFER,
3194            gl::COLOR_ATTACHMENT0,
3195            gl::TEXTURE_2D,
3196            self.texture_id,
3197            0,
3198        );
3199        gl.viewport(0, 0, self.size.width as i32, self.size.height as i32);
3200        gl.enable(gl::BLEND);
3201        gl.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
3202        gl.use_program(prog);
3203
3204        let a = (f32::from(brush.color.a) / 255.0) * brush.flow.clamp(0.0, 1.0);
3205        gl.uniform_4f(
3206            gl.get_uniform_location(prog, "uColor"),
3207            f32::from(brush.color.r) / 255.0,
3208            f32::from(brush.color.g) / 255.0,
3209            f32::from(brush.color.b) / 255.0,
3210            a,
3211        );
3212        gl.uniform_1f(gl.get_uniform_location(prog, "uHardness"), brush.hardness);
3213
3214        gl.bind_buffer(gl::ARRAY_BUFFER, vbo);
3215        gl.enable_vertex_attrib_array(0);
3216        gl.enable_vertex_attrib_array(1);
3217        gl.vertex_attrib_pointer_f32(0, 2, false, 16, 0);
3218        gl.vertex_attrib_pointer_f32(1, 2, false, 16, 8);
3219
3220        let dx = x1 - x0;
3221        let dy = y1 - y0;
3222        let len = dx.hypot(dy);
3223        let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
3224        let n = ((len / step).floor() as i32).max(0);
3225        let r = brush.radius;
3226        for i in 0..=n {
3227            let t = if n == 0 { 1.0 } else { i as f32 / n as f32 };
3228            let px = x0 + dx * t;
3229            let py = y0 + dy * t;
3230            // dab bbox -> NDC; y is flipped so (0,0) is top-left like the CPU path.
3231            let nx = |x: f32| (x / tw) * 2.0 - 1.0;
3232            let ny = |y: f32| 1.0 - (y / th) * 2.0;
3233            let (l, rr, tp, bt) = (nx(px - r), nx(px + r), ny(py - r), ny(py + r));
3234            // TRIANGLE_STRIP: TL, BL, TR, BR -- interleaved (pos.x, pos.y, uv.x, uv.y).
3235            let verts: [f32; 16] = [
3236                l, tp, -1.0, -1.0, l, bt, -1.0, 1.0, rr, tp, 1.0, -1.0, rr, bt, 1.0, 1.0,
3237            ];
3238            gl.buffer_data_untyped(
3239                gl::ARRAY_BUFFER,
3240                (verts.len() * size_of::<f32>()) as isize,
3241                GlVoidPtrConst {
3242                    ptr: verts.as_ptr() as *const GLvoid,
3243                    run_destructor: false,
3244                },
3245                gl::STREAM_DRAW,
3246            );
3247            gl.draw_arrays(gl::TRIANGLE_STRIP, 0, 4);
3248        }
3249
3250        gl.disable_vertex_attrib_array(0);
3251        gl.disable_vertex_attrib_array(1);
3252        gl.bind_buffer(gl::ARRAY_BUFFER, 0);
3253        gl.disable(gl::BLEND);
3254        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
3255        gl.delete_buffers((&[vbo][..]).into());
3256        gl.delete_framebuffers((&[fbo][..]).into());
3257    }
3258
3259    /// Read this texture's pixels back into an RGBA8 `RawImage` (top-left origin)
3260    /// -- for exporting the painted canvas to disk. Binds an FBO + glReadPixels.
3261    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
3262    #[must_use] pub fn copy_to_raw_image(&self) -> RawImage {
3263        let gl = self.gl_context.clone();
3264        let (w, h) = (self.size.width as i32, self.size.height as i32);
3265        if self.texture_id == 0 || w <= 0 || h <= 0 {
3266            return RawImage::null_image();
3267        }
3268        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
3269        if fbo == 0 {
3270            return RawImage::null_image();
3271        }
3272        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
3273        gl.framebuffer_texture_2d(
3274            gl::FRAMEBUFFER,
3275            gl::COLOR_ATTACHMENT0,
3276            gl::TEXTURE_2D,
3277            self.texture_id,
3278            0,
3279        );
3280        let pixels = gl.read_pixels(0, 0, w, h, gl::RGBA, gl::UNSIGNED_BYTE);
3281        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
3282        gl.delete_framebuffers((&[fbo][..]).into());
3283
3284        // glReadPixels uses a bottom-left origin; flip rows to top-left for saving.
3285        let mut bytes = pixels.into_library_owned_vec();
3286        let row = (w as usize) * 4;
3287        let hh = h as usize;
3288        if row > 0 && bytes.len() >= row * hh {
3289            for y in 0..hh / 2 {
3290                let yi = y * row;
3291                let yo = (hh - 1 - y) * row;
3292                for k in 0..row {
3293                    bytes.swap(yi + k, yo + k);
3294                }
3295            }
3296        }
3297        RawImage {
3298            pixels: RawImageData::U8(bytes.into()),
3299            width: w as usize,
3300            height: h as usize,
3301            premultiplied_alpha: true,
3302            data_format: RawImageFormat::RGBA8,
3303            tag: Vec::new().into(),
3304        }
3305    }
3306}
3307
3308impl_traits_for_gl_object!(Texture, texture_id);
3309
3310impl Drop for Texture {
3311    fn drop(&mut self) {
3312        // AUDIT: mirror `GlContextPtr::drop`. Without this guard a C-ABI
3313        // double-drop (drop_in_place run twice on the same byte-copied struct)
3314        // does a second `fetch_sub` on the already-freed refcount box (UAF) and
3315        // a second `delete_textures`. The first drop clears `run_destructor`, so
3316        // the second is a no-op.
3317        if !self.run_destructor {
3318            return;
3319        }
3320        self.run_destructor = false;
3321        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3322        if copies == 1 {
3323            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3324            self.gl_context
3325                .delete_textures((&[self.texture_id])[..].into());
3326        }
3327    }
3328}
3329
3330/// Describes the vertex layout and offsets
3331#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3332#[repr(C)]
3333pub struct VertexLayout {
3334    pub fields: VertexAttributeVec,
3335}
3336
3337impl_vec!(VertexAttribute, VertexAttributeVec, VertexAttributeVecDestructor, VertexAttributeVecDestructorType, VertexAttributeVecSlice, OptionVertexAttribute);
3338impl_vec_debug!(VertexAttribute, VertexAttributeVec);
3339impl_vec_partialord!(VertexAttribute, VertexAttributeVec);
3340impl_vec_ord!(VertexAttribute, VertexAttributeVec);
3341impl_vec_clone!(
3342    VertexAttribute,
3343    VertexAttributeVec,
3344    VertexAttributeVecDestructor
3345);
3346impl_vec_partialeq!(VertexAttribute, VertexAttributeVec);
3347impl_vec_eq!(VertexAttribute, VertexAttributeVec);
3348impl_vec_hash!(VertexAttribute, VertexAttributeVec);
3349
3350impl VertexLayout {
3351    /// Submits the vertex buffer description to OpenGL
3352    // OpenGL binding: vertex-attribute layout (locations, item counts, strides,
3353    // offsets) passed to the gl API as GLuint/GLint/GLsizei; values are GL-bounded.
3354    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
3355    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3356    pub fn bind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
3357        const VERTICES_ARE_NORMALIZED: bool = false;
3358
3359        let mut offset = 0;
3360
3361        let stride_between_vertices: usize =
3362            self.fields.iter().map(VertexAttribute::get_stride).sum();
3363
3364        for vertex_attribute in &self.fields {
3365            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
3366                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
3367                |ll| *ll as i32,
3368            );
3369
3370            gl_context.vertex_attrib_pointer(
3371                attribute_location as u32,
3372                vertex_attribute.item_count as i32,
3373                vertex_attribute.attribute_type.get_gl_id(),
3374                VERTICES_ARE_NORMALIZED,
3375                stride_between_vertices as i32,
3376                offset as u32,
3377            );
3378            gl_context.enable_vertex_attrib_array(attribute_location as u32);
3379            offset += vertex_attribute.get_stride();
3380        }
3381    }
3382
3383    /// Unsets the vertex buffer description
3384    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3385    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts
3386    pub fn unbind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
3387        for vertex_attribute in &self.fields {
3388            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
3389                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
3390                |ll| *ll as i32,
3391            );
3392            gl_context.disable_vertex_attrib_array(attribute_location as u32);
3393        }
3394    }
3395}
3396
3397#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3398#[repr(C)]
3399pub struct VertexAttribute {
3400    /// Attribute name of the vertex attribute in the vertex shader, i.e. `"vAttrXY"`
3401    pub va_name: AzString,
3402    /// If the vertex shader has a specific location, (like `layout(location = 2) vAttrXY`),
3403    /// use this instead of the name to look up the uniform location.
3404    pub layout_location: OptionUsize,
3405    /// Type of items of this attribute (i.e. for a `FloatVec2`, would be
3406    /// `VertexAttributeType::Float`)
3407    pub attribute_type: VertexAttributeType,
3408    /// Number of items of this attribute (i.e. for a `FloatVec2`, would be `2` (= 2 consecutive
3409    /// f32 values))
3410    pub item_count: usize,
3411}
3412
3413impl_option!(
3414    VertexAttribute,
3415    OptionVertexAttribute,
3416    copy = false,
3417    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3418);
3419
3420impl VertexAttribute {
3421    #[must_use] pub const fn get_stride(&self) -> usize {
3422        self.attribute_type.get_mem_size() * self.item_count
3423    }
3424}
3425
3426#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3427#[repr(C)]
3428pub enum VertexAttributeType {
3429    /// Vertex attribute has type `f32`
3430    Float,
3431    /// Vertex attribute has type `f64`
3432    Double,
3433    /// Vertex attribute has type `u8`
3434    UnsignedByte,
3435    /// Vertex attribute has type `u16`
3436    UnsignedShort,
3437    /// Vertex attribute has type `u32`
3438    UnsignedInt,
3439}
3440
3441impl VertexAttributeType {
3442    /// Returns the OpenGL id for the vertex attribute type, ex. `gl::UNSIGNED_BYTE` for
3443    /// `VertexAttributeType::UnsignedByte`.
3444    #[must_use] pub const fn get_gl_id(&self) -> GLuint {
3445        use self::VertexAttributeType::{Float, Double, UnsignedByte, UnsignedShort, UnsignedInt};
3446        match self {
3447            Float => gl::FLOAT,
3448            Double => gl::DOUBLE,
3449            UnsignedByte => gl::UNSIGNED_BYTE,
3450            UnsignedShort => gl::UNSIGNED_SHORT,
3451            UnsignedInt => gl::UNSIGNED_INT,
3452        }
3453    }
3454
3455    #[must_use] pub const fn get_mem_size(&self) -> usize {
3456        use core::mem;
3457
3458        use self::VertexAttributeType::{Float, Double, UnsignedByte, UnsignedShort, UnsignedInt};
3459        match self {
3460            Float => size_of::<f32>(),
3461            Double => size_of::<f64>(),
3462            UnsignedByte => size_of::<u8>(),
3463            UnsignedShort => size_of::<u16>(),
3464            UnsignedInt => size_of::<u32>(),
3465        }
3466    }
3467}
3468
3469pub trait VertexLayoutDescription {
3470    fn get_description() -> VertexLayout;
3471}
3472
3473#[derive(Debug, PartialEq, Eq, PartialOrd)]
3474#[repr(C)]
3475pub struct VertexArrayObject {
3476    pub vertex_layout: VertexLayout,
3477    pub vao_id: GLuint,
3478    pub gl_context: GlContextPtr,
3479    pub refcount: *const AtomicUsize,
3480    pub run_destructor: bool,
3481}
3482
3483impl VertexArrayObject {
3484    #[must_use] pub fn new(vertex_layout: VertexLayout, vao_id: GLuint, gl_context: GlContextPtr) -> Self {
3485        Self {
3486            vertex_layout,
3487            vao_id,
3488            gl_context,
3489            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3490            run_destructor: true,
3491        }
3492    }
3493}
3494
3495impl Clone for VertexArrayObject {
3496    fn clone(&self) -> Self {
3497        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
3498        Self {
3499            vertex_layout: self.vertex_layout.clone(),
3500            vao_id: self.vao_id,
3501            gl_context: self.gl_context.clone(),
3502            refcount: self.refcount,
3503            run_destructor: true,
3504        }
3505    }
3506}
3507
3508impl Drop for VertexArrayObject {
3509    fn drop(&mut self) {
3510        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
3511        // freeing the refcount box twice (use-after-free) + double delete.
3512        if !self.run_destructor {
3513            return;
3514        }
3515        self.run_destructor = false;
3516        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3517        if copies == 1 {
3518            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3519            self.gl_context
3520                .delete_vertex_arrays((&[self.vao_id])[..].into());
3521        }
3522    }
3523}
3524
3525#[repr(C)]
3526pub struct VertexBuffer {
3527    pub vao: VertexArrayObject,
3528    pub vertex_buffer_id: GLuint,
3529    pub vertex_buffer_len: usize,
3530    pub index_buffer_id: GLuint,
3531    pub index_buffer_len: usize,
3532    pub refcount: *const AtomicUsize,
3533    pub index_buffer_format: IndexBufferFormat,
3534    pub run_destructor: bool,
3535}
3536
3537impl fmt::Display for VertexBuffer {
3538    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3539        write!(
3540            f,
3541            "VertexBuffer {{ buffer: {} (length: {}) }})",
3542            self.vertex_buffer_id, self.vertex_buffer_len
3543        )
3544    }
3545}
3546
3547impl_traits_for_gl_object!(VertexBuffer, vertex_buffer_id);
3548
3549impl Clone for VertexBuffer {
3550    fn clone(&self) -> Self {
3551        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
3552        Self {
3553            vao: self.vao.clone(),
3554            vertex_buffer_id: self.vertex_buffer_id,
3555            vertex_buffer_len: self.vertex_buffer_len,
3556            index_buffer_id: self.index_buffer_id,
3557            index_buffer_len: self.index_buffer_len,
3558            refcount: self.refcount,
3559            index_buffer_format: self.index_buffer_format,
3560            run_destructor: true,
3561        }
3562    }
3563}
3564
3565impl Drop for VertexBuffer {
3566    fn drop(&mut self) {
3567        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
3568        // freeing the refcount box twice (use-after-free) + double delete.
3569        if !self.run_destructor {
3570            return;
3571        }
3572        self.run_destructor = false;
3573        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3574        if copies == 1 {
3575            self.vao.vertex_layout = VertexLayout {
3576                fields: VertexAttributeVec::from_const_slice(&[]),
3577            };
3578            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3579            self.vao
3580                .gl_context
3581                .delete_buffers((&[self.vertex_buffer_id, self.index_buffer_id])[..].into());
3582        }
3583    }
3584}
3585
3586impl VertexBuffer {
3587    /// # Panics
3588    ///
3589    /// Panics if the GL driver failed to create the vertex-array/buffer objects
3590    /// (the returned id lists are empty).
3591    // OpenGL binding: buffer sizes / vertex counts passed to the gl API as
3592    // GLsizeiptr/GLint; values are GL-bounded.
3593    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
3594    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3595    pub fn new<T: VertexLayoutDescription>(
3596        gl_context: GlContextPtr,
3597        shader_program_id: GLuint,
3598        vertices: &[T],
3599        indices: &[u32],
3600        index_buffer_format: IndexBufferFormat,
3601    ) -> Self {
3602        use core::mem;
3603
3604        // Save the OpenGL state
3605        let mut current_vertex_array = [0_i32];
3606
3607        gl_context.get_integer_v(gl::VERTEX_ARRAY, (&mut current_vertex_array[..]).into());
3608
3609        let vertex_array_object = gl_context.gen_vertex_arrays(1);
3610        let vertex_array_object = vertex_array_object.get(0).unwrap();
3611
3612        let vertex_buffer_id = gl_context.gen_buffers(1);
3613        let vertex_buffer_id = vertex_buffer_id.get(0).unwrap();
3614
3615        let index_buffer_id = gl_context.gen_buffers(1);
3616        let index_buffer_id = index_buffer_id.get(0).unwrap();
3617
3618        gl_context.bind_vertex_array(*vertex_array_object);
3619
3620        // Upload vertex data to GPU
3621        gl_context.bind_buffer(gl::ARRAY_BUFFER, *vertex_buffer_id);
3622        gl_context.buffer_data_untyped(
3623            gl::ARRAY_BUFFER,
3624            size_of_val(vertices) as isize,
3625            GlVoidPtrConst {
3626                ptr: vertices.as_ptr() as *const core::ffi::c_void,
3627                run_destructor: true,
3628            },
3629            gl::STATIC_DRAW,
3630        );
3631
3632        // Generate the index buffer + upload data
3633        gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, *index_buffer_id);
3634        gl_context.buffer_data_untyped(
3635            gl::ELEMENT_ARRAY_BUFFER,
3636            size_of_val(indices) as isize,
3637            GlVoidPtrConst {
3638                ptr: indices.as_ptr() as *const core::ffi::c_void,
3639                run_destructor: true,
3640            },
3641            gl::STATIC_DRAW,
3642        );
3643
3644        let vertex_description = T::get_description();
3645        vertex_description.bind(&gl_context.ptr.ptr, shader_program_id);
3646
3647        // Reset the OpenGL state
3648        gl_context.bind_vertex_array(current_vertex_array[0] as u32);
3649
3650        Self::new_raw(
3651            *vertex_buffer_id,
3652            vertices.len(),
3653            VertexArrayObject::new(vertex_description, *vertex_array_object, gl_context),
3654            *index_buffer_id,
3655            indices.len(),
3656            index_buffer_format,
3657        )
3658    }
3659
3660    #[must_use] pub fn new_raw(
3661        vertex_buffer_id: GLuint,
3662        vertex_buffer_len: usize,
3663        vao: VertexArrayObject,
3664        index_buffer_id: GLuint,
3665        index_buffer_len: usize,
3666        index_buffer_format: IndexBufferFormat,
3667    ) -> Self {
3668        Self {
3669            vertex_buffer_id,
3670            vertex_buffer_len,
3671            vao,
3672            index_buffer_id,
3673            index_buffer_len,
3674            index_buffer_format,
3675            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3676            run_destructor: true,
3677        }
3678    }
3679}
3680
3681#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3682pub enum GlApiVersion {
3683    Gl { major: usize, minor: usize },
3684    GlEs { major: usize, minor: usize },
3685}
3686
3687impl GlApiVersion {
3688    /// Returns the OpenGL version of the context
3689    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
3690    #[must_use] pub fn get(gl_context: &GlContextPtr) -> Self {
3691        let mut major = [0];
3692        gl_context.get_integer_v(gl::MAJOR_VERSION, (&mut major[..]).into());
3693        let mut minor = [0];
3694        gl_context.get_integer_v(gl::MINOR_VERSION, (&mut minor[..]).into());
3695
3696        let major = major[0] as usize;
3697        let minor = minor[0] as usize;
3698
3699        match gl_context.get_type() {
3700            GlType::Gl => Self::Gl { major, minor },
3701            GlType::Gles => Self::GlEs { major, minor },
3702        }
3703    }
3704}
3705
3706#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3707#[repr(C)]
3708pub enum IndexBufferFormat {
3709    Points,
3710    Lines,
3711    LineStrip,
3712    Triangles,
3713    TriangleStrip,
3714    TriangleFan,
3715}
3716
3717impl IndexBufferFormat {
3718    /// Returns the `gl::TRIANGLE_STRIP` / `gl::POINTS`, etc.
3719    #[must_use] pub const fn get_gl_id(&self) -> GLuint {
3720        use self::IndexBufferFormat::{Points, Lines, LineStrip, Triangles, TriangleStrip, TriangleFan};
3721        match self {
3722            Points => gl::POINTS,
3723            Lines => gl::LINES,
3724            LineStrip => gl::LINE_STRIP,
3725            Triangles => gl::TRIANGLES,
3726            TriangleStrip => gl::TRIANGLE_STRIP,
3727            TriangleFan => gl::TRIANGLE_FAN,
3728        }
3729    }
3730}
3731
3732#[derive(Debug, Clone, PartialEq, PartialOrd)]
3733#[repr(C)]
3734pub struct Uniform {
3735    pub uniform_name: AzString,
3736    pub uniform_type: UniformType,
3737}
3738
3739impl Uniform {
3740    pub fn create<S: Into<AzString>>(name: S, uniform_type: UniformType) -> Self {
3741        Self {
3742            uniform_name: name.into(),
3743            uniform_type,
3744        }
3745    }
3746}
3747
3748#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
3749#[repr(C, u8)]
3750pub enum UniformType {
3751    Float(f32),
3752    FloatVec2([f32; 2]),
3753    FloatVec3([f32; 3]),
3754    FloatVec4([f32; 4]),
3755    Int(i32),
3756    IntVec2([i32; 2]),
3757    IntVec3([i32; 3]),
3758    IntVec4([i32; 4]),
3759    UnsignedInt(u32),
3760    UnsignedIntVec2([u32; 2]),
3761    UnsignedIntVec3([u32; 3]),
3762    UnsignedIntVec4([u32; 4]),
3763    Matrix2 {
3764        transpose: bool,
3765        matrix: [f32; 2 * 2],
3766    },
3767    Matrix3 {
3768        transpose: bool,
3769        matrix: [f32; 3 * 3],
3770    },
3771    Matrix4 {
3772        transpose: bool,
3773        matrix: [f32; 4 * 4],
3774    },
3775}
3776
3777impl UniformType {
3778    /// Set a specific uniform
3779    pub fn set(self, gl_context: &Rc<GenericGlContext>, location: GLint) {
3780        use self::UniformType::{Float, FloatVec2, FloatVec3, FloatVec4, Int, IntVec2, IntVec3, IntVec4, UnsignedInt, UnsignedIntVec2, UnsignedIntVec3, UnsignedIntVec4, Matrix2, Matrix3, Matrix4};
3781        match self {
3782            Float(r) => gl_context.uniform_1f(location, r),
3783            FloatVec2([r, g]) => gl_context.uniform_2f(location, r, g),
3784            FloatVec3([r, g, b]) => gl_context.uniform_3f(location, r, g, b),
3785            FloatVec4([r, g, b, a]) => gl_context.uniform_4f(location, r, g, b, a),
3786            Int(r) => gl_context.uniform_1i(location, r),
3787            IntVec2([r, g]) => gl_context.uniform_2i(location, r, g),
3788            IntVec3([r, g, b]) => gl_context.uniform_3i(location, r, g, b),
3789            IntVec4([r, g, b, a]) => gl_context.uniform_4i(location, r, g, b, a),
3790            UnsignedInt(r) => gl_context.uniform_1ui(location, r),
3791            UnsignedIntVec2([r, g]) => gl_context.uniform_2ui(location, r, g),
3792            UnsignedIntVec3([r, g, b]) => gl_context.uniform_3ui(location, r, g, b),
3793            UnsignedIntVec4([r, g, b, a]) => gl_context.uniform_4ui(location, r, g, b, a),
3794            Matrix2 { transpose, matrix } => {
3795                gl_context.uniform_matrix_2fv(location, transpose, &matrix[..]);
3796            }
3797            Matrix3 { transpose, matrix } => {
3798                gl_context.uniform_matrix_3fv(location, transpose, &matrix[..]);
3799            }
3800            Matrix4 { transpose, matrix } => {
3801                gl_context.uniform_matrix_4fv(location, transpose, &matrix[..]);
3802            }
3803        }
3804    }
3805}
3806
3807#[repr(C)]
3808pub struct GlShader {
3809    pub program_id: GLuint,
3810    pub gl_context: GlContextPtr,
3811    /// AUDIT: guards against a double-drop deleting the same GL program twice
3812    /// (`drop_in_place` run twice on a byte-copied struct). Set `true` on
3813    /// construction; the first drop clears it so a second drop is a no-op.
3814    pub run_destructor: bool,
3815}
3816
3817impl ::core::fmt::Display for GlShader {
3818    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3819        write!(f, "GlShader {{ program_id: {} }}", self.program_id)
3820    }
3821}
3822
3823impl_traits_for_gl_object!(GlShader, program_id);
3824
3825impl Drop for GlShader {
3826    fn drop(&mut self) {
3827        // AUDIT: mirror `GlContextPtr::drop` — a C-ABI double-drop would call
3828        // `delete_program` on the same id twice. The first drop clears the flag.
3829        if !self.run_destructor {
3830            return;
3831        }
3832        self.run_destructor = false;
3833        self.gl_context.delete_program(self.program_id);
3834    }
3835}
3836
3837#[repr(C)]
3838#[derive(Clone)]
3839pub struct VertexShaderCompileError {
3840    pub error_id: i32,
3841    pub info_log: AzString,
3842}
3843
3844impl_traits_for_gl_object!(VertexShaderCompileError, error_id);
3845
3846impl ::core::fmt::Display for VertexShaderCompileError {
3847    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3848        write!(f, "E{}: {}", self.error_id, self.info_log)
3849    }
3850}
3851
3852#[repr(C)]
3853#[derive(Clone)]
3854pub struct FragmentShaderCompileError {
3855    pub error_id: i32,
3856    pub info_log: AzString,
3857}
3858
3859impl_traits_for_gl_object!(FragmentShaderCompileError, error_id);
3860
3861impl ::core::fmt::Display for FragmentShaderCompileError {
3862    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3863        write!(f, "E{}: {}", self.error_id, self.info_log)
3864    }
3865}
3866
3867#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
3868pub enum GlShaderCompileError {
3869    Vertex(VertexShaderCompileError),
3870    Fragment(FragmentShaderCompileError),
3871}
3872
3873impl ::core::fmt::Display for GlShaderCompileError {
3874    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3875        use self::GlShaderCompileError::{Vertex, Fragment};
3876        match self {
3877            Vertex(vert_err) => write!(f, "Failed to compile vertex shader: {vert_err}"),
3878            Fragment(frag_err) => write!(f, "Failed to compile fragment shader: {frag_err}"),
3879        }
3880    }
3881}
3882
3883impl ::core::fmt::Debug for GlShaderCompileError {
3884    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3885        write!(f, "{self}")
3886    }
3887}
3888
3889#[repr(C)]
3890#[derive(Clone)]
3891pub struct GlShaderLinkError {
3892    pub error_id: i32,
3893    pub info_log: AzString,
3894}
3895
3896impl_traits_for_gl_object!(GlShaderLinkError, error_id);
3897
3898impl ::core::fmt::Display for GlShaderLinkError {
3899    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3900        write!(f, "E{}: {}", self.error_id, self.info_log)
3901    }
3902}
3903
3904#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
3905pub enum GlShaderCreateError {
3906    Compile(GlShaderCompileError),
3907    Link(GlShaderLinkError),
3908    NoShaderCompiler,
3909}
3910
3911impl ::core::fmt::Display for GlShaderCreateError {
3912    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3913        use self::GlShaderCreateError::{Compile, Link, NoShaderCompiler};
3914        match self {
3915            Compile(compile_err) => write!(f, "Shader compile error: {compile_err}"),
3916            Link(link_err) => write!(f, "Shader linking error: {link_err}"),
3917            NoShaderCompiler => {
3918                write!(f, "OpenGL implementation doesn't include a shader compiler")
3919            }
3920        }
3921    }
3922}
3923
3924impl ::core::fmt::Debug for GlShaderCreateError {
3925    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3926        write!(f, "{self}")
3927    }
3928}
3929
3930impl GlShader {
3931    /// Compiles and creates a new OpenGL shader, created from a vertex and a fragment shader
3932    /// string.
3933    ///
3934    /// If the shader fails to compile, the shader object gets automatically deleted, no cleanup
3935    /// necessary.
3936    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3937    /// # Errors
3938    ///
3939    /// Returns an error if the OpenGL implementation has no shader compiler, or if the vertex/fragment shader fails to compile or link.
3940    pub fn new(
3941        gl_context: &GlContextPtr,
3942        vertex_shader: &str,
3943        fragment_shader: &str,
3944    ) -> Result<Self, GlShaderCreateError> {
3945        // Check whether the OpenGL implementation supports a shader compiler...
3946        let mut shader_compiler_supported = [gl::FALSE as u8];
3947        gl_context.get_boolean_v(
3948            gl::SHADER_COMPILER,
3949            (&mut shader_compiler_supported[..]).into(),
3950        );
3951        if u32::from(shader_compiler_supported[0]) == gl::FALSE {
3952            // Implementation only supports binary shaders
3953            return Err(GlShaderCreateError::NoShaderCompiler);
3954        }
3955
3956        // Compile vertex shader
3957
3958        let vertex_shader_object = gl_context.create_shader(gl::VERTEX_SHADER);
3959        gl_context.shader_source(
3960            vertex_shader_object,
3961            vec![AzString::from(vertex_shader.to_string())].into(),
3962        );
3963        gl_context.compile_shader(vertex_shader_object);
3964
3965        if let Some(error_id) = get_gl_shader_error(gl_context, vertex_shader_object) {
3966            let info_log = gl_context.get_shader_info_log(vertex_shader_object);
3967            gl_context.delete_shader(vertex_shader_object);
3968            return Err(GlShaderCreateError::Compile(GlShaderCompileError::Vertex(
3969                VertexShaderCompileError {
3970                    error_id,
3971                    info_log,
3972                },
3973            )));
3974        }
3975
3976        // Compile fragment shader
3977
3978        let fragment_shader_object = gl_context.create_shader(gl::FRAGMENT_SHADER);
3979        gl_context.shader_source(
3980            fragment_shader_object,
3981            vec![AzString::from(fragment_shader.to_string())].into(),
3982        );
3983        gl_context.compile_shader(fragment_shader_object);
3984
3985        if let Some(error_id) = get_gl_shader_error(gl_context, fragment_shader_object) {
3986            let info_log = gl_context.get_shader_info_log(fragment_shader_object);
3987            gl_context.delete_shader(vertex_shader_object);
3988            gl_context.delete_shader(fragment_shader_object);
3989            return Err(GlShaderCreateError::Compile(
3990                GlShaderCompileError::Fragment(FragmentShaderCompileError {
3991                    error_id,
3992                    info_log,
3993                }),
3994            ));
3995        }
3996
3997        // Link program
3998
3999        let program_id = gl_context.create_program();
4000        gl_context.attach_shader(program_id, vertex_shader_object);
4001        gl_context.attach_shader(program_id, fragment_shader_object);
4002        gl_context.link_program(program_id);
4003
4004        if let Some(error_id) = get_gl_program_error(gl_context, program_id) {
4005            let info_log = gl_context.get_program_info_log(program_id);
4006            gl_context.delete_shader(vertex_shader_object);
4007            gl_context.delete_shader(fragment_shader_object);
4008            gl_context.delete_program(program_id);
4009            return Err(GlShaderCreateError::Link(GlShaderLinkError {
4010                error_id,
4011                info_log,
4012            }));
4013        }
4014
4015        gl_context.delete_shader(vertex_shader_object);
4016        gl_context.delete_shader(fragment_shader_object);
4017
4018        Ok(Self {
4019            program_id,
4020            gl_context: gl_context.clone(),
4021            run_destructor: true,
4022        })
4023    }
4024
4025    /// Draws vertex buffers, index buffers + uniforms to the texture
4026    ///
4027    /// # Panics
4028    ///
4029    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
4030    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4031    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
4032    pub fn draw(
4033        // shader to use for drawing
4034        shader_program_id: GLuint,
4035        // note: texture is &mut so the texture is reusable -
4036        texture: &mut Texture,
4037        // buffers + uniforms to draw
4038        buffers: &[(&VertexBuffer, &[Uniform])],
4039    ) {
4040        use alloc::collections::btree_map::BTreeMap;
4041
4042        const INDEX_TYPE: GLuint = gl::UNSIGNED_INT;
4043
4044        let texture_size = texture.size;
4045
4046        let gl_context = &texture.gl_context;
4047
4048        let saved = GlStateSave::save(gl_context);
4049
4050        // save draw()-specific state not covered by GlStateSave
4051        let mut current_blend_enabled = [0_u8];
4052        let mut current_primitive_restart_enabled = [0_u8];
4053        gl_context.get_boolean_v(gl::BLEND, (&mut current_blend_enabled[..]).into());
4054        gl_context.get_boolean_v(
4055            gl::PRIMITIVE_RESTART,
4056            (&mut current_primitive_restart_enabled[..]).into(),
4057        );
4058
4059        // 1. Create the framebuffer
4060        let framebuffers = gl_context.gen_framebuffers(1);
4061        let framebuffer_id = *framebuffers.get(0).unwrap();
4062        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
4063        // panic anywhere below (incl. `gen_renderbuffers().get(0).unwrap()`)
4064        // can't leak the FBO/RBO. Guard's Drop runs on unwind too.
4065        let mut fbo_rbo_guard = FboRboGuard {
4066            gl_context,
4067            framebuffer_id,
4068            renderbuffer_id: 0,
4069        };
4070        gl_context.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
4071
4072        let depthbuffers = gl_context.gen_renderbuffers(1);
4073        let depthbuffer_id = *depthbuffers.get(0).unwrap();
4074        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
4075
4076        gl_context.bind_texture(gl::TEXTURE_2D, texture.texture_id);
4077        gl_context.tex_image_2d(
4078            gl::TEXTURE_2D,
4079            0,
4080            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
4081            texture_size.width as i32,
4082            texture_size.height as i32,
4083            0,
4084            gl::RGBA, // gl::BGRA?
4085            gl::UNSIGNED_BYTE,
4086            None.into(),
4087        );
4088        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
4089        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
4090        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
4091        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
4092
4093        gl_context.bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
4094        gl_context.renderbuffer_storage(
4095            gl::RENDERBUFFER,
4096            gl::DEPTH_COMPONENT,
4097            texture_size.width as i32,
4098            texture_size.height as i32,
4099        );
4100        gl_context.framebuffer_renderbuffer(
4101            gl::FRAMEBUFFER,
4102            gl::DEPTH_ATTACHMENT,
4103            gl::RENDERBUFFER,
4104            depthbuffer_id,
4105        );
4106
4107        gl_context.framebuffer_texture_2d(
4108            gl::FRAMEBUFFER,
4109            gl::COLOR_ATTACHMENT0,
4110            gl::TEXTURE_2D,
4111            texture.texture_id,
4112            0,
4113        );
4114        gl_context.draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
4115
4116        #[cfg(feature = "std")]
4117        {
4118            let fb_check = gl_context.check_frame_buffer_status(gl::FRAMEBUFFER);
4119            match fb_check {
4120                gl::FRAMEBUFFER_COMPLETE => {}
4121                gl::FRAMEBUFFER_UNDEFINED => {
4122                    println!("GL_FRAMEBUFFER_UNDEFINED");
4123                }
4124                gl::FRAMEBUFFER_INCOMPLETE_ATTACHMENT => {
4125                    println!("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
4126                }
4127                gl::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT => {
4128                    println!("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
4129                }
4130                gl::FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER => {
4131                    println!("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
4132                }
4133                gl::FRAMEBUFFER_INCOMPLETE_READ_BUFFER => {
4134                    println!("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
4135                }
4136                gl::FRAMEBUFFER_UNSUPPORTED => {
4137                    println!("GL_FRAMEBUFFER_UNSUPPORTED");
4138                }
4139                gl::FRAMEBUFFER_INCOMPLETE_MULTISAMPLE => {
4140                    println!("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
4141                }
4142                gl::FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS => {
4143                    println!("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS");
4144                }
4145                o => {
4146                    println!("glFramebufferStatus returned unknown return code: {o}");
4147                }
4148            }
4149        }
4150
4151        gl_context.viewport(0, 0, texture_size.width as i32, texture_size.height as i32);
4152        gl_context.enable(gl::BLEND);
4153        // Use GL_PRIMITIVE_RESTART (OpenGL 3.1+) instead of
4154        // GL_PRIMITIVE_RESTART_FIXED_INDEX (4.3+) for macOS compatibility.
4155        gl_context.enable(gl::PRIMITIVE_RESTART);
4156        unsafe {
4157            let gl = gl_context.get();
4158            if !gl.glPrimitiveRestartIndex.is_null() {
4159                let func: extern "system" fn(u32) =
4160                    core::mem::transmute(gl.glPrimitiveRestartIndex);
4161                func(GL_RESTART_INDEX); // u32::MAX
4162            }
4163        }
4164        gl_context.disable(gl::MULTISAMPLE);
4165        gl_context.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); // TODO: enable / disable
4166        gl_context.use_program(shader_program_id);
4167
4168        // Avoid multiple calls to get_uniform_location by caching the uniform locations
4169        let mut uniform_locations: BTreeMap<AzString, i32> = BTreeMap::new();
4170        let mut max_uniform_len = 0;
4171        for (_, uniforms) in buffers {
4172            for uniform in *uniforms {
4173                if !uniform_locations.contains_key(&uniform.uniform_name) {
4174                    uniform_locations.insert(
4175                        uniform.uniform_name.clone(),
4176                        gl_context.get_uniform_location(
4177                            shader_program_id,
4178                            uniform.uniform_name.as_str(),
4179                        ),
4180                    );
4181                }
4182            }
4183            max_uniform_len = max_uniform_len.max(uniforms.len());
4184        }
4185        let mut current_uniforms = vec![None; max_uniform_len];
4186
4187        // Since the description of the vertex buffers is always the same,
4188        // only the first layer needs to bind its VAO
4189
4190        // Draw the actual layers
4191        for (vertex_index_buffer, uniforms) in buffers {
4192            gl_context.bind_vertex_array(vertex_index_buffer.vao.vao_id);
4193            gl_context.bind_buffer(gl::ARRAY_BUFFER, vertex_index_buffer.vertex_buffer_id);
4194            gl_context.bind_buffer(
4195                gl::ELEMENT_ARRAY_BUFFER,
4196                vertex_index_buffer.index_buffer_id,
4197            );
4198
4199            // Only set the uniform if the value has changed
4200            for (uniform_index, uniform) in uniforms.iter().enumerate() {
4201                if current_uniforms[uniform_index] != Some(uniform.uniform_type) {
4202                    let uniform_location = uniform_locations[&uniform.uniform_name];
4203                    uniform.uniform_type.set(gl_context.get(), uniform_location);
4204                    current_uniforms[uniform_index] = Some(uniform.uniform_type);
4205                }
4206            }
4207
4208            gl_context.draw_elements(
4209                vertex_index_buffer.index_buffer_format.get_gl_id(),
4210                vertex_index_buffer.index_buffer_len as i32,
4211                INDEX_TYPE,
4212                0,
4213            );
4214        }
4215
4216        // Reset draw()-specific state
4217        if u32::from(current_blend_enabled[0]) == gl::FALSE {
4218            gl_context.disable(gl::BLEND);
4219        }
4220        if u32::from(current_primitive_restart_enabled[0]) == gl::FALSE {
4221            gl_context.disable(gl::PRIMITIVE_RESTART);
4222        }
4223
4224        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
4225        // also runs on an unwinding panic) — reclaim them explicitly here so
4226        // the deletion order matches the original (delete before texture
4227        // metadata writes / after state restore).
4228        // Reset common GL state
4229        saved.restore(gl_context);
4230        drop(fbo_rbo_guard);
4231
4232        texture.format = RawImageFormat::RGBA8;
4233        texture.flags = TextureFlags {
4234            is_opaque: false,
4235            is_video_texture: false,
4236        };
4237    }
4238}
4239
4240#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4241fn get_gl_shader_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
4242    let mut err = [0];
4243    context.get_shader_iv(shader_object, gl::COMPILE_STATUS, (&mut err[..]).into());
4244    let err_code = err[0];
4245    if err_code == gl::TRUE as i32 {
4246        None
4247    } else {
4248        Some(err_code)
4249    }
4250}
4251
4252#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4253fn get_gl_program_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
4254    let mut err = [0];
4255    context.get_program_iv(shader_object, gl::LINK_STATUS, (&mut err[..]).into());
4256    let err_code = err[0];
4257    if err_code == gl::TRUE as i32 {
4258        None
4259    } else {
4260        Some(err_code)
4261    }
4262}
4263
4264#[cfg(test)]
4265mod audit_tests {
4266    use super::*;
4267
4268    // AUDIT: FFI slice/str accessors must return an empty slice (not form a
4269    // slice over a null/dangling ptr, which is UB) when handed a null or
4270    // zero-length descriptor from C.
4271    #[test]
4272    fn refstr_null_and_empty_is_empty_str() {
4273        let null = Refstr {
4274            ptr: core::ptr::null(),
4275            len: 0,
4276        };
4277        assert_eq!(null.as_str(), "");
4278
4279        // null ptr but nonzero len (garbage from FFI) must also not deref.
4280        let null_lenful = Refstr {
4281            ptr: core::ptr::null(),
4282            len: 5,
4283        };
4284        assert_eq!(null_lenful.as_str(), "");
4285
4286        let s = "hello";
4287        let good: Refstr = s.into();
4288        assert_eq!(good.as_str(), "hello");
4289    }
4290
4291    #[test]
4292    fn refstr_vec_ref_null_is_empty_slice() {
4293        let null = RefstrVecRef {
4294            ptr: core::ptr::null(),
4295            len: 3,
4296        };
4297        assert!(null.as_slice().is_empty());
4298    }
4299
4300    #[test]
4301    fn u8_vec_ref_null_is_empty_slice() {
4302        let null = U8VecRef {
4303            ptr: core::ptr::null(),
4304            len: 8,
4305        };
4306        assert!(null.as_slice().is_empty());
4307
4308        let data = [1u8, 2, 3];
4309        let good: U8VecRef = (&data[..]).into();
4310        assert_eq!(good.as_slice(), &[1, 2, 3]);
4311    }
4312}
4313
4314#[cfg(test)]
4315#[allow(clippy::float_cmp)] // exact f32 compares are the point: these assert bit-exact round-trips / lossy-cast values
4316mod autotest_generated {
4317    use super::*;
4318
4319    // ---------------------------------------------------------------------
4320    // Test scaffolding: a "null" GL context.
4321    //
4322    // Every field of `GenericGlContext` is a `*mut c_void` function pointer
4323    // (780 of them, no other field types), and every gl-context-loader method
4324    // null-checks its pointer and returns a default (0 / empty Vec / "") rather
4325    // than calling through. So an all-zero context is a SAFE no-op GL driver:
4326    // it lets us drive azul's whole wrapper surface off-GPU and assert how the
4327    // wrappers behave when the driver hands back degenerate values -- which is
4328    // exactly the "broken driver" path `is_gl_usable()` exists for.
4329    // ---------------------------------------------------------------------
4330    fn null_gl() -> Rc<GenericGlContext> {
4331        // SAFETY: `GenericGlContext` is `repr(C)` and every field is a raw
4332        // pointer, for which the all-zero bit pattern (null) is valid.
4333        Rc::new(unsafe { core::mem::zeroed::<GenericGlContext>() })
4334    }
4335
4336    fn null_ctx() -> GlContextPtr {
4337        GlContextPtr::new(RendererType::Software, null_gl())
4338    }
4339
4340    fn test_texture(texture_id: GLuint, width: u32, height: u32) -> Texture {
4341        Texture::create(
4342            texture_id,
4343            TextureFlags {
4344                is_opaque: false,
4345                is_video_texture: false,
4346            },
4347            PhysicalSizeU32 { width, height },
4348            ColorU {
4349                r: 1,
4350                g: 2,
4351                b: 3,
4352                a: 4,
4353            },
4354            null_ctx(),
4355            RawImageFormat::RGBA8,
4356        )
4357    }
4358
4359    const ALL_ATTRIB_TYPES: [VertexAttributeType; 5] = [
4360        VertexAttributeType::Float,
4361        VertexAttributeType::Double,
4362        VertexAttributeType::UnsignedByte,
4363        VertexAttributeType::UnsignedShort,
4364        VertexAttributeType::UnsignedInt,
4365    ];
4366
4367    const ALL_INDEX_FORMATS: [IndexBufferFormat; 6] = [
4368        IndexBufferFormat::Points,
4369        IndexBufferFormat::Lines,
4370        IndexBufferFormat::LineStrip,
4371        IndexBufferFormat::Triangles,
4372        IndexBufferFormat::TriangleStrip,
4373        IndexBufferFormat::TriangleFan,
4374    ];
4375
4376    // =====================================================================
4377    // Refstr / *VecRef / *VecRefMut -- FFI slice+str accessors
4378    // (parsers: malformed / huge / boundary / unicode)
4379    // =====================================================================
4380
4381    #[test]
4382    fn refstr_roundtrips_unicode_multibyte_and_combining_marks() {
4383        // Non-ASCII, emoji, and combining marks must survive byte-exactly:
4384        // `as_str` rebuilds the str via `from_utf8_unchecked` over the raw bytes.
4385        for s in [
4386            "\u{1F600}",
4387            "日本語のテキスト",
4388            "e\u{0301}\u{0328}combining",
4389            "\u{0}nul\u{0}embedded\u{0}",
4390            "\u{FFFD}\u{200B}zero-width",
4391        ] {
4392            let r: Refstr = s.into();
4393            assert_eq!(r.as_str(), s);
4394            assert_eq!(r.as_str().len(), s.len());
4395        }
4396    }
4397
4398    #[test]
4399    fn refstr_len_zero_over_nonempty_buffer_is_empty_not_dangling() {
4400        // len == 0 must short-circuit even when ptr is a perfectly valid buffer.
4401        let backing = "not empty";
4402        let r = Refstr {
4403            ptr: backing.as_ptr(),
4404            len: 0,
4405        };
4406        assert_eq!(r.as_str(), "");
4407    }
4408
4409    #[test]
4410    fn refstr_huge_input_does_not_hang() {
4411        // 1 MB single-token string: as_str is O(1) (no scan), so this must be instant.
4412        let huge = "x".repeat(1_000_000);
4413        let r: Refstr = huge.as_str().into();
4414        assert_eq!(r.as_str().len(), 1_000_000);
4415        assert_eq!(r.as_str(), huge.as_str());
4416    }
4417
4418    #[test]
4419    fn refstr_debug_on_null_does_not_panic() {
4420        let null = Refstr {
4421            ptr: core::ptr::null(),
4422            len: usize::MAX,
4423        };
4424        // Debug goes through as_str(); a null guard failure here would be UB, not a panic.
4425        assert_eq!(alloc::format!("{null:?}"), "\"\"");
4426    }
4427
4428    #[test]
4429    fn refstr_clone_preserves_ptr_and_len() {
4430        let s = "clone me";
4431        let r: Refstr = s.into();
4432        let c = r.clone();
4433        assert_eq!(c.as_str(), s);
4434        assert!(core::ptr::eq(c.ptr, r.ptr));
4435        assert_eq!(c.len, r.len);
4436    }
4437
4438    #[test]
4439    fn every_vec_ref_with_null_ptr_and_nonzero_len_is_empty() {
4440        // The FFI boundary can hand us a null ptr with a garbage (nonzero) len.
4441        // Forming a slice over null is UB, so every accessor must return empty.
4442        const GARBAGE_LEN: usize = usize::MAX;
4443
4444        assert!(RefstrVecRef {
4445            ptr: core::ptr::null(),
4446            len: GARBAGE_LEN
4447        }
4448        .as_slice()
4449        .is_empty());
4450        assert!(GLuintVecRef {
4451            ptr: core::ptr::null(),
4452            len: GARBAGE_LEN
4453        }
4454        .as_slice()
4455        .is_empty());
4456        assert!(GLenumVecRef {
4457            ptr: core::ptr::null(),
4458            len: GARBAGE_LEN
4459        }
4460        .as_slice()
4461        .is_empty());
4462        assert!(U8VecRef {
4463            ptr: core::ptr::null(),
4464            len: GARBAGE_LEN
4465        }
4466        .as_slice()
4467        .is_empty());
4468        assert!(F32VecRef {
4469            ptr: core::ptr::null(),
4470            len: GARBAGE_LEN
4471        }
4472        .as_slice()
4473        .is_empty());
4474        assert!(I32VecRef {
4475            ptr: core::ptr::null(),
4476            len: GARBAGE_LEN
4477        }
4478        .as_slice()
4479        .is_empty());
4480
4481        // ...and the same for the mutable variants, through BOTH accessors.
4482        let mut m64 = GLint64VecRefMut {
4483            ptr: core::ptr::null_mut(),
4484            len: GARBAGE_LEN,
4485        };
4486        assert!(m64.as_slice().is_empty());
4487        assert!(m64.as_mut_slice().is_empty());
4488
4489        let mut mf = GLfloatVecRefMut {
4490            ptr: core::ptr::null_mut(),
4491            len: GARBAGE_LEN,
4492        };
4493        assert!(mf.as_slice().is_empty());
4494        assert!(mf.as_mut_slice().is_empty());
4495
4496        let mut mi = GLintVecRefMut {
4497            ptr: core::ptr::null_mut(),
4498            len: GARBAGE_LEN,
4499        };
4500        assert!(mi.as_slice().is_empty());
4501        assert!(mi.as_mut_slice().is_empty());
4502
4503        let mut mb = GLbooleanVecRefMut {
4504            ptr: core::ptr::null_mut(),
4505            len: GARBAGE_LEN,
4506        };
4507        assert!(mb.as_slice().is_empty());
4508        assert!(mb.as_mut_slice().is_empty());
4509
4510        let mut mu8 = U8VecRefMut {
4511            ptr: core::ptr::null_mut(),
4512            len: GARBAGE_LEN,
4513        };
4514        assert!(mu8.as_slice().is_empty());
4515        assert!(mu8.as_mut_slice().is_empty());
4516    }
4517
4518    #[test]
4519    fn vec_refs_roundtrip_from_slice() {
4520        let u: [GLuint; 3] = [0, 1, u32::MAX];
4521        assert_eq!(GLuintVecRef::from(&u[..]).as_slice(), &u[..]);
4522
4523        let e: [GLenum; 2] = [gl::TRIANGLES, u32::MAX];
4524        assert_eq!(GLenumVecRef::from(&e[..]).as_slice(), &e[..]);
4525
4526        let b: [u8; 4] = [0, 127, 128, 255];
4527        assert_eq!(U8VecRef::from(&b[..]).as_slice(), &b[..]);
4528
4529        let i: [i32; 3] = [i32::MIN, 0, i32::MAX];
4530        assert_eq!(I32VecRef::from(&i[..]).as_slice(), &i[..]);
4531
4532        // Empty (but non-null) slices must also come back empty.
4533        let empty: [u8; 0] = [];
4534        assert!(U8VecRef::from(&empty[..]).as_slice().is_empty());
4535    }
4536
4537    #[test]
4538    fn f32_vec_ref_preserves_nan_inf_and_subnormals_bit_exactly() {
4539        // These are pointer casts, not conversions: NaN payloads and -0.0 must
4540        // survive bit-for-bit (a NaN-normalizing copy would be a real bug).
4541        let vals: [f32; 6] = [
4542            f32::NAN,
4543            f32::INFINITY,
4544            f32::NEG_INFINITY,
4545            -0.0,
4546            f32::MIN_POSITIVE / 2.0, // subnormal
4547            f32::MAX,
4548        ];
4549        let r = F32VecRef::from(&vals[..]);
4550        let got = r.as_slice();
4551        assert_eq!(got.len(), vals.len());
4552        for (g, v) in got.iter().zip(vals.iter()) {
4553            assert_eq!(g.to_bits(), v.to_bits());
4554        }
4555        assert!(got[0].is_nan());
4556        assert!(got[3].is_sign_negative());
4557    }
4558
4559    #[test]
4560    fn glint64_vec_ref_mut_handles_boundary_values() {
4561        let mut vals: [GLint64; 4] = [i64::MIN, -1, 0, i64::MAX];
4562        let mut r = GLint64VecRefMut::from(&mut vals[..]);
4563        assert_eq!(r.as_slice(), &[i64::MIN, -1, 0, i64::MAX]);
4564
4565        // Writes through as_mut_slice must land in the caller's buffer.
4566        r.as_mut_slice()[0] = i64::MAX;
4567        r.as_mut_slice()[3] = i64::MIN;
4568        assert_eq!(vals, [i64::MAX, -1, 0, i64::MIN]);
4569    }
4570
4571    #[test]
4572    fn mut_vec_refs_write_through_to_the_caller_buffer() {
4573        let mut floats: [GLfloat; 2] = [0.0, 0.0];
4574        GLfloatVecRefMut::from(&mut floats[..]).as_mut_slice()[1] = f32::NAN;
4575        assert!(floats[1].is_nan());
4576
4577        let mut ints: [GLint; 2] = [0, 0];
4578        GLintVecRefMut::from(&mut ints[..]).as_mut_slice()[0] = i32::MIN;
4579        assert_eq!(ints[0], i32::MIN);
4580
4581        let mut bools: [GLboolean; 2] = [0, 0];
4582        GLbooleanVecRefMut::from(&mut bools[..]).as_mut_slice()[1] = 255;
4583        assert_eq!(bools[1], 255);
4584
4585        let mut bytes: [u8; 3] = [1, 2, 3];
4586        U8VecRefMut::from(&mut bytes[..]).as_mut_slice()[2] = 0;
4587        assert_eq!(bytes, [1, 2, 0]);
4588    }
4589
4590    #[test]
4591    fn u8_vec_ref_ord_eq_hash_agree_with_the_underlying_slice() {
4592        use core::hash::{BuildHasher, Hasher};
4593
4594        use alloc::collections::BTreeSet;
4595
4596        let a = [1u8, 2, 3];
4597        let b = [1u8, 2, 4];
4598        let ra = U8VecRef::from(&a[..]);
4599        let rb = U8VecRef::from(&b[..]);
4600
4601        assert_eq!(ra, U8VecRef::from(&a[..]));
4602        assert!(ra < rb);
4603        assert_eq!(ra.cmp(&rb), a[..].cmp(&b[..]));
4604
4605        // A null ref must compare equal to an empty one (both are the empty slice).
4606        let null = U8VecRef {
4607            ptr: core::ptr::null(),
4608            len: 99,
4609        };
4610        let empty: [u8; 0] = [];
4611        assert_eq!(null, U8VecRef::from(&empty[..]));
4612
4613        // Hash must be consistent with Eq (equal values -> equal hashes).
4614        fn hash_of(v: &U8VecRef) -> u64 {
4615            let mut h = core::hash::BuildHasherDefault::<TestHasher>::default().build_hasher();
4616            v.hash(&mut h);
4617            h.finish()
4618        }
4619        #[derive(Default)]
4620        struct TestHasher(u64);
4621        impl Hasher for TestHasher {
4622            fn finish(&self) -> u64 {
4623                self.0
4624            }
4625            fn write(&mut self, bytes: &[u8]) {
4626                for b in bytes {
4627                    self.0 = self.0.wrapping_mul(31).wrapping_add(u64::from(*b));
4628                }
4629            }
4630        }
4631        assert_eq!(hash_of(&ra), hash_of(&U8VecRef::from(&a[..])));
4632        assert_eq!(hash_of(&null), hash_of(&U8VecRef::from(&empty[..])));
4633
4634        // Ord must be a total order good enough for a BTreeSet.
4635        let mut set = BTreeSet::new();
4636        set.insert(ra.clone());
4637        set.insert(U8VecRef::from(&a[..]));
4638        set.insert(rb);
4639        assert_eq!(set.len(), 2);
4640    }
4641
4642    #[test]
4643    fn refstr_vec_ref_roundtrips_and_maps_back_to_strs() {
4644        let strs = ["", "a", "\u{1F600}"];
4645        let refstrs: Vec<Refstr> = strs.iter().map(|s| Refstr::from(*s)).collect();
4646        let vec_ref = RefstrVecRef::from(&refstrs[..]);
4647        let got: Vec<&str> = vec_ref.as_slice().iter().map(Refstr::as_str).collect();
4648        assert_eq!(got, strs);
4649    }
4650
4651    // =====================================================================
4652    // GLsyncPtr (constructor + round-trip)
4653    // =====================================================================
4654
4655    #[test]
4656    fn glsync_ptr_roundtrips_null_and_nonnull() {
4657        let null = GLsyncPtr::new(core::ptr::null());
4658        assert!(null.clone().get().is_null());
4659        assert_eq!(alloc::format!("{null:?}"), "0x0");
4660
4661        // A non-null (never dereferenced) sentinel must round-trip identically.
4662        let sentinel = usize::MAX as *const c_void;
4663        let p = GLsyncPtr::new(sentinel);
4664        assert_eq!(p.clone().get() as usize, usize::MAX);
4665        assert!(p.run_destructor);
4666    }
4667
4668    // =====================================================================
4669    // shader_with_glsl_version (parser: the `#version` line swap)
4670    // =====================================================================
4671
4672    #[cfg(feature = "std")]
4673    #[test]
4674    fn shader_with_glsl_version_replaces_only_the_first_line() {
4675        let src = b"#version 150\nbody line 1\nbody line 2";
4676        let out = shader_with_glsl_version(src, b"#version 300 es\n");
4677        assert_eq!(out, b"#version 300 es\nbody line 1\nbody line 2".to_vec());
4678    }
4679
4680    #[cfg(feature = "std")]
4681    #[test]
4682    fn shader_with_glsl_version_empty_src_yields_just_the_version_line() {
4683        // No newline in src -> body_start = 0 (the map_or default), so the whole
4684        // (empty) src is kept and only the version line is prepended.
4685        assert_eq!(
4686            shader_with_glsl_version(b"", b"#version 150\n"),
4687            b"#version 150\n".to_vec()
4688        );
4689    }
4690
4691    #[cfg(feature = "std")]
4692    #[test]
4693    fn shader_with_glsl_version_src_without_newline_is_kept_whole() {
4694        // A src with NO newline has no first line to strip: body_start stays 0,
4695        // so the version line is prepended and nothing is lost.
4696        let out = shader_with_glsl_version(b"void main(){}", b"#version 150\n");
4697        assert_eq!(out, b"#version 150\nvoid main(){}".to_vec());
4698    }
4699
4700    #[cfg(feature = "std")]
4701    #[test]
4702    fn shader_with_glsl_version_leading_newline_drops_only_that_newline() {
4703        let out = shader_with_glsl_version(b"\nrest", b"V\n");
4704        assert_eq!(out, b"V\nrest".to_vec());
4705    }
4706
4707    #[cfg(feature = "std")]
4708    #[test]
4709    fn shader_with_glsl_version_empty_version_line_still_strips_first_line() {
4710        let out = shader_with_glsl_version(b"#version 150\nbody", b"");
4711        assert_eq!(out, b"body".to_vec());
4712
4713        // Both empty -> empty, no panic.
4714        assert!(shader_with_glsl_version(b"", b"").is_empty());
4715    }
4716
4717    #[cfg(feature = "std")]
4718    #[test]
4719    fn shader_with_glsl_version_is_byte_exact_on_invalid_utf8_and_nul_bytes() {
4720        // Shader sources are bytes, not str: invalid UTF-8 / NULs must pass
4721        // through untouched (this is fed straight to glShaderSource).
4722        let src = b"#version 150\n\xFF\xFE\x00\x80body";
4723        let out = shader_with_glsl_version(src, b"#version 100\n");
4724        assert_eq!(out, b"#version 100\n\xFF\xFE\x00\x80body".to_vec());
4725
4726        // A src that is ONLY invalid bytes with no newline is kept whole.
4727        let out2 = shader_with_glsl_version(&[0xFF, 0xFE, 0x00], b"V\n");
4728        assert_eq!(out2, vec![b'V', b'\n', 0xFF, 0xFE, 0x00]);
4729    }
4730
4731    #[cfg(feature = "std")]
4732    #[test]
4733    fn shader_with_glsl_version_handles_a_1mb_source_without_hanging() {
4734        let mut src = b"#version 150\n".to_vec();
4735        src.resize(src.len() + 1_000_000, b'x');
4736        let out = shader_with_glsl_version(&src, b"#version 300 es\n");
4737        assert_eq!(out.len(), b"#version 300 es\n".len() + 1_000_000);
4738        assert!(out.starts_with(b"#version 300 es\n"));
4739        assert!(out.ends_with(b"xxxx"));
4740    }
4741
4742    // =====================================================================
4743    // glsl_version_candidates (invariants the version probe relies on)
4744    // =====================================================================
4745
4746    #[test]
4747    fn glsl_version_candidates_are_wellformed_and_distinct() {
4748        for gl_type in [GlType::Gl, GlType::Gles] {
4749            let candidates = glsl_version_candidates(gl_type);
4750            assert!(
4751                !candidates.is_empty(),
4752                "{gl_type:?} must have at least one candidate or the probe can never succeed"
4753            );
4754            for c in candidates {
4755                // GlContextPtr::new parses these back out with
4756                // `trim().trim_start_matches("#version ")`, which requires
4757                // valid UTF-8, the exact prefix, and a trailing newline.
4758                let s = core::str::from_utf8(c).expect("candidate must be valid UTF-8");
4759                assert!(s.starts_with("#version "), "{s:?}");
4760                assert!(s.ends_with('\n'), "{s:?}");
4761                let parsed = s.trim().trim_start_matches("#version ");
4762                assert!(!parsed.is_empty(), "{s:?} must parse to a nonempty version");
4763            }
4764            // No duplicates: a repeated candidate would just re-probe a known failure.
4765            for (i, a) in candidates.iter().enumerate() {
4766                for b in candidates.iter().skip(i + 1) {
4767                    assert_ne!(a, b, "duplicate candidate in {gl_type:?}");
4768                }
4769            }
4770        }
4771
4772        // The GL and GLES lists must be disjoint (a GLES driver rejects `#version 150`).
4773        for g in glsl_version_candidates(GlType::Gl) {
4774            assert!(!glsl_version_candidates(GlType::Gles).contains(g));
4775        }
4776
4777        // Documented examples from the API docs: "150" (GL) and "300 es" (GLES).
4778        let first_gl = core::str::from_utf8(glsl_version_candidates(GlType::Gl)[0]).unwrap();
4779        assert_eq!(first_gl.trim().trim_start_matches("#version "), "150");
4780        let first_es = core::str::from_utf8(glsl_version_candidates(GlType::Gles)[0]).unwrap();
4781        assert_eq!(first_es.trim().trim_start_matches("#version "), "300 es");
4782    }
4783
4784    #[test]
4785    fn gl_type_from_context_gl_type_is_total() {
4786        assert_eq!(GlType::from(GlContextGlType::Gl), GlType::Gl);
4787        assert_eq!(GlType::from(GlContextGlType::GlEs), GlType::Gles);
4788    }
4789
4790    // =====================================================================
4791    // GlContextPtr -- construction + the documented "unusable driver" fallback
4792    // =====================================================================
4793
4794    #[test]
4795    fn gl_context_ptr_software_is_never_usable_and_has_no_shaders() {
4796        // Documented: "Always `false` for a Software context (which never
4797        // compiles these shaders)".
4798        let ctx = GlContextPtr::new(RendererType::Software, null_gl());
4799        assert!(!ctx.is_gl_usable());
4800        assert_eq!(ctx.get_svg_shader(), 0);
4801        assert_eq!(ctx.get_brush_shader(), 0);
4802        assert_eq!(ctx.get_fxaa_shader(), 0);
4803        assert_eq!(ctx.get_usable_glsl_version().as_str(), "");
4804        assert_eq!(ctx.renderer_type, RendererType::Software);
4805    }
4806
4807    #[test]
4808    fn gl_context_ptr_hardware_with_broken_driver_falls_back_instead_of_panicking() {
4809        // THE contract `is_gl_usable()` exists for: context creation "succeeds"
4810        // but the driver compiles nothing. The probe must try every candidate
4811        // version, fail them all, and leave every program id at 0 -- so the
4812        // caller can fall back to CPU rendering -- rather than panicking or
4813        // handing out a garbage program id.
4814        let ctx = GlContextPtr::new(RendererType::Hardware, null_gl());
4815        assert!(
4816            !ctx.is_gl_usable(),
4817            "a driver that compiles nothing must report is_gl_usable() == false"
4818        );
4819        assert_eq!(ctx.get_svg_shader(), 0);
4820        assert_eq!(ctx.get_brush_shader(), 0);
4821        assert_eq!(ctx.get_fxaa_shader(), 0);
4822        assert_eq!(
4823            ctx.get_usable_glsl_version().as_str(),
4824            "",
4825            "glsl_version must be empty when the context is unusable"
4826        );
4827    }
4828
4829    #[test]
4830    fn gl_context_ptr_get_type_defaults_to_desktop_gl_on_an_empty_version_string() {
4831        // get_type() sniffs the GL_VERSION string for "OpenGL ES"; a driver that
4832        // returns nothing must deterministically fall out as desktop Gl.
4833        assert_eq!(null_ctx().get_type(), GlType::Gl);
4834    }
4835
4836    #[test]
4837    fn gl_context_ptr_clone_is_eq_but_distinct_contexts_are_not() {
4838        // Eq/Ord are identity-based (`as_usize` = the inner Rc address), so a
4839        // clone must compare equal and two independently created contexts must not.
4840        let a = null_ctx();
4841        let clone = a.clone();
4842        assert_eq!(a, clone);
4843        assert_eq!(a.cmp(&clone), core::cmp::Ordering::Equal);
4844        assert_eq!(a.partial_cmp(&clone), Some(core::cmp::Ordering::Equal));
4845
4846        let b = null_ctx();
4847        assert_ne!(a, b);
4848        // Ord must be antisymmetric and consistent with PartialOrd.
4849        assert_eq!(a.cmp(&b), a.partial_cmp(&b).unwrap());
4850        assert_eq!(a.cmp(&b).reverse(), b.cmp(&a));
4851
4852        // The clone must point at the same underlying GL context.
4853        assert!(Rc::ptr_eq(a.get(), clone.get()));
4854        assert!(!Rc::ptr_eq(a.get(), b.get()));
4855    }
4856
4857    #[test]
4858    fn gl_context_ptr_gen_family_returns_empty_for_every_n_including_negatives() {
4859        // A driver that generates nothing returns an EMPTY list. Callers index
4860        // into this ([0] / .get(0).unwrap()), so the wrappers must at least not
4861        // panic on the way out -- and must not choke on a negative/extreme n.
4862        let ctx = null_ctx();
4863        for n in [0, 1, -1, i32::MIN, i32::MAX] {
4864            assert!(ctx.gen_buffers(n).as_slice().is_empty(), "gen_buffers({n})");
4865            assert!(ctx.gen_textures(n).as_slice().is_empty(), "gen_textures({n})");
4866            assert!(
4867                ctx.gen_framebuffers(n).as_slice().is_empty(),
4868                "gen_framebuffers({n})"
4869            );
4870            assert!(
4871                ctx.gen_renderbuffers(n).as_slice().is_empty(),
4872                "gen_renderbuffers({n})"
4873            );
4874            assert!(
4875                ctx.gen_vertex_arrays(n).as_slice().is_empty(),
4876                "gen_vertex_arrays({n})"
4877            );
4878            assert!(ctx.gen_queries(n).as_slice().is_empty(), "gen_queries({n})");
4879        }
4880    }
4881
4882    #[test]
4883    fn gl_context_ptr_integer_extremes_do_not_panic() {
4884        // Offsets/sizes/limits at the boundary of their integer types. These are
4885        // passed straight through to GL, so the wrapper must not do arithmetic
4886        // that overflows on the way.
4887        let ctx = null_ctx();
4888        let data = [0u8; 4];
4889        let void_ptr = || GlVoidPtrConst {
4890            ptr: data.as_ptr().cast(),
4891            run_destructor: false,
4892        };
4893
4894        for offset in [0isize, -1, isize::MIN, isize::MAX] {
4895            for size in [0isize, -1, isize::MIN, isize::MAX] {
4896                ctx.buffer_sub_data_untyped(gl::ARRAY_BUFFER, offset, size, void_ptr());
4897                let _ = ctx.map_buffer_range(gl::ARRAY_BUFFER, offset, size, 0);
4898            }
4899        }
4900        ctx.buffer_data_untyped(gl::ARRAY_BUFFER, isize::MIN, void_ptr(), gl::STATIC_DRAW);
4901
4902        // usize offset at the extreme (reinterpreted as a byte offset pointer).
4903        for offset in [0usize, 1, usize::MAX] {
4904            ctx.tex_sub_image_2d_pbo(
4905                gl::TEXTURE_2D,
4906                0,
4907                0,
4908                0,
4909                1,
4910                1,
4911                gl::RGBA,
4912                gl::UNSIGNED_BYTE,
4913                offset,
4914            );
4915        }
4916
4917        ctx.pixel_store_i(gl::PACK_ALIGNMENT, i32::MIN);
4918        ctx.pixel_store_i(gl::PACK_ALIGNMENT, i32::MAX);
4919        let _ = ctx.unmap_buffer(gl::ARRAY_BUFFER);
4920    }
4921
4922    #[test]
4923    fn gl_context_ptr_float_extremes_do_not_panic() {
4924        // NaN / inf / subnormal floats must pass through the f32 wrappers untouched.
4925        let ctx = null_ctx();
4926        for v in [
4927            0.0,
4928            1.0,
4929            -1.0,
4930            f32::NAN,
4931            f32::INFINITY,
4932            f32::NEG_INFINITY,
4933            f32::MIN,
4934            f32::MAX,
4935        ] {
4936            ctx.sample_coverage(v, false);
4937            ctx.sample_coverage(v, true);
4938            ctx.polygon_offset(v, v);
4939        }
4940    }
4941
4942    #[test]
4943    fn gl_context_ptr_shader_source_handles_empty_unicode_and_embedded_nuls() {
4944        // shader_source NUL-terminates each string itself; an already-embedded
4945        // NUL or multibyte UTF-8 must not panic on the way through.
4946        let ctx = null_ctx();
4947        let strings: StringVec = vec![
4948            AzString::from(String::new()),
4949            AzString::from("\u{1F600} emoji".to_string()),
4950            AzString::from("has\u{0}nul".to_string()),
4951            AzString::from("x".repeat(100_000)),
4952        ]
4953        .into();
4954        ctx.shader_source(0, strings);
4955
4956        // An empty list of sources must also be fine.
4957        ctx.shader_source(u32::MAX, Vec::<AzString>::new().into());
4958    }
4959
4960    #[test]
4961    fn gl_context_ptr_read_pixels_sizes_the_buffer_from_the_dimensions() {
4962        // NOTE: only SAFE (small, positive) dimensions are exercised here.
4963        // Negative dimensions are a live hazard in this path -- see the report:
4964        // gl-context-loader's read_pixels does
4965        //   `vec![0; width as usize * height as usize * bit_depth]`
4966        // BEFORE its null-pointer check, so a negative width sign-extends to
4967        // ~usize::MAX and the multiply overflows (debug) / requests an
4968        // exabyte-scale allocation (release). Deliberately not provoked.
4969        let ctx = null_ctx();
4970        let px = ctx.read_pixels(0, 0, 2, 3, gl::RGBA, gl::UNSIGNED_BYTE);
4971        assert_eq!(px.len(), 2 * 3 * 4, "RGBA/UNSIGNED_BYTE = 4 bytes per pixel");
4972
4973        // A zero-size read is the degenerate-but-safe case.
4974        assert_eq!(
4975            ctx.read_pixels(0, 0, 0, 0, gl::RGBA, gl::UNSIGNED_BYTE).len(),
4976            0
4977        );
4978    }
4979
4980    #[test]
4981    fn gl_context_ptr_read_pixels_into_buffer_accepts_null_and_undersized_targets() {
4982        // The destination is caller-owned here (no allocation from the dims), so
4983        // a null / empty / undersized target must simply no-op.
4984        let ctx = null_ctx();
4985        ctx.read_pixels_into_buffer(
4986            0,
4987            0,
4988            4,
4989            4,
4990            gl::RGBA,
4991            gl::UNSIGNED_BYTE,
4992            U8VecRefMut {
4993                ptr: core::ptr::null_mut(),
4994                len: 64,
4995            },
4996        );
4997
4998        let mut small = [0u8; 1];
4999        ctx.read_pixels_into_buffer(
5000            0,
5001            0,
5002            4,
5003            4,
5004            gl::RGBA,
5005            gl::UNSIGNED_BYTE,
5006            (&mut small[..]).into(),
5007        );
5008        assert_eq!(small, [0u8; 1]);
5009    }
5010
5011    #[test]
5012    fn gl_context_ptr_uniform_getters_accept_null_result_buffers() {
5013        let ctx = null_ctx();
5014        ctx.get_uniform_iv(
5015            0,
5016            -1,
5017            GLintVecRefMut {
5018                ptr: core::ptr::null_mut(),
5019                len: 16,
5020            },
5021        );
5022        ctx.get_uniform_fv(
5023            0,
5024            i32::MIN,
5025            GLfloatVecRefMut {
5026                ptr: core::ptr::null_mut(),
5027                len: 16,
5028            },
5029        );
5030
5031        // ...and real buffers, at extreme locations.
5032        let mut ints = [0i32; 2];
5033        ctx.get_uniform_iv(u32::MAX, i32::MAX, (&mut ints[..]).into());
5034        let mut floats = [0f32; 2];
5035        ctx.get_uniform_fv(u32::MAX, i32::MAX, (&mut floats[..]).into());
5036    }
5037
5038    #[test]
5039    fn gl_context_ptr_get_uniform_indices_handles_empty_and_null_name_lists() {
5040        let ctx = null_ctx();
5041        let empty: &[Refstr] = &[];
5042        assert!(ctx
5043            .get_uniform_indices(0, empty.into())
5044            .as_slice()
5045            .is_empty());
5046        assert!(ctx
5047            .get_uniform_indices(
5048                0,
5049                RefstrVecRef {
5050                    ptr: core::ptr::null(),
5051                    len: 4,
5052                },
5053            )
5054            .as_slice()
5055            .is_empty());
5056    }
5057
5058    #[test]
5059    fn gl_context_ptr_delete_family_accepts_empty_and_null_id_lists() {
5060        // Deleting nothing must be a no-op, not an out-of-bounds read.
5061        let ctx = null_ctx();
5062        let empty: &[GLuint] = &[];
5063        ctx.delete_buffers(empty.into());
5064        ctx.delete_textures(empty.into());
5065        ctx.delete_framebuffers(empty.into());
5066        ctx.delete_renderbuffers(empty.into());
5067        ctx.delete_vertex_arrays(empty.into());
5068        ctx.delete_queries(empty.into());
5069
5070        let null = || GLuintVecRef {
5071            ptr: core::ptr::null(),
5072            len: 7,
5073        };
5074        ctx.delete_buffers(null());
5075        ctx.delete_textures(null());
5076        ctx.delete_framebuffers(null());
5077        ctx.delete_renderbuffers(null());
5078        ctx.delete_vertex_arrays(null());
5079        ctx.delete_queries(null());
5080
5081        // A real (bogus) id list, incl. 0 and u32::MAX, must also be fine.
5082        let ids: &[GLuint] = &[0, 1, u32::MAX];
5083        ctx.delete_buffers(ids.into());
5084        ctx.delete_textures(ids.into());
5085    }
5086
5087    #[test]
5088    fn gl_context_ptr_draw_buffers_accepts_an_empty_list() {
5089        let ctx = null_ctx();
5090        let empty: &[GLenum] = &[];
5091        ctx.draw_buffers(empty.into());
5092        ctx.draw_buffers(
5093            GLenumVecRef {
5094                ptr: core::ptr::null(),
5095                len: 3,
5096            },
5097        );
5098    }
5099
5100    // =====================================================================
5101    // VertexAttributeType / VertexAttribute / VertexLayout (numeric + invariants)
5102    // =====================================================================
5103
5104    #[test]
5105    fn vertex_attribute_type_mem_size_matches_the_rust_type_it_names() {
5106        assert_eq!(
5107            VertexAttributeType::Float.get_mem_size(),
5108            core::mem::size_of::<f32>()
5109        );
5110        assert_eq!(
5111            VertexAttributeType::Double.get_mem_size(),
5112            core::mem::size_of::<f64>()
5113        );
5114        assert_eq!(
5115            VertexAttributeType::UnsignedByte.get_mem_size(),
5116            core::mem::size_of::<u8>()
5117        );
5118        assert_eq!(
5119            VertexAttributeType::UnsignedShort.get_mem_size(),
5120            core::mem::size_of::<u16>()
5121        );
5122        assert_eq!(
5123            VertexAttributeType::UnsignedInt.get_mem_size(),
5124            core::mem::size_of::<u32>()
5125        );
5126
5127        // Every size must be nonzero -- a 0 would make get_stride() collapse to 0
5128        // and silently produce a degenerate vertex layout.
5129        for t in ALL_ATTRIB_TYPES {
5130            assert!(t.get_mem_size() > 0, "{t:?}");
5131        }
5132    }
5133
5134    #[test]
5135    fn vertex_attribute_type_gl_ids_are_distinct_and_nonzero() {
5136        for (i, a) in ALL_ATTRIB_TYPES.iter().enumerate() {
5137            assert_ne!(a.get_gl_id(), 0, "{a:?} maps to the GL 'no type' id 0");
5138            for b in ALL_ATTRIB_TYPES.iter().skip(i + 1) {
5139                assert_ne!(
5140                    a.get_gl_id(),
5141                    b.get_gl_id(),
5142                    "{a:?} and {b:?} share a GL id -- one of them would upload as the wrong type"
5143                );
5144            }
5145        }
5146        assert_eq!(VertexAttributeType::Float.get_gl_id(), gl::FLOAT);
5147        assert_eq!(
5148            VertexAttributeType::UnsignedByte.get_gl_id(),
5149            gl::UNSIGNED_BYTE
5150        );
5151    }
5152
5153    #[test]
5154    fn vertex_attribute_get_stride_at_zero_and_at_the_overflow_boundary() {
5155        let attr = |ty, item_count| VertexAttribute {
5156            va_name: AzString::from_const_str("vAttrXY"),
5157            layout_location: OptionUsize::None,
5158            attribute_type: ty,
5159            item_count,
5160        };
5161
5162        // Zero items -> zero stride.
5163        for t in ALL_ATTRIB_TYPES {
5164            assert_eq!(attr(t, 0).get_stride(), 0, "{t:?}");
5165        }
5166
5167        // Exact multiplication for representative counts.
5168        assert_eq!(attr(VertexAttributeType::Float, 2).get_stride(), 8);
5169        assert_eq!(attr(VertexAttributeType::Double, 4).get_stride(), 32);
5170        assert_eq!(attr(VertexAttributeType::UnsignedByte, 3).get_stride(), 3);
5171
5172        // The LARGEST item_count that does not overflow `mem_size * item_count`.
5173        // (NOTE: get_stride() is a plain `*`, so an item_count above this bound
5174        // overflow-panics in debug / wraps in release -- see the report.)
5175        for t in ALL_ATTRIB_TYPES {
5176            let max_items = usize::MAX / t.get_mem_size();
5177            assert_eq!(
5178                attr(t, max_items).get_stride(),
5179                max_items * t.get_mem_size(),
5180                "{t:?} at the overflow boundary"
5181            );
5182        }
5183    }
5184
5185    #[test]
5186    fn vertex_layout_stride_is_the_sum_of_its_fields() {
5187        let attr = |name: &str, ty, item_count| VertexAttribute {
5188            va_name: AzString::from(name.to_string()),
5189            layout_location: OptionUsize::None,
5190            attribute_type: ty,
5191            item_count,
5192        };
5193
5194        // Empty layout: zero fields, zero total stride.
5195        let empty = VertexLayout {
5196            fields: VertexAttributeVec::from_const_slice(&[]),
5197        };
5198        assert_eq!(
5199            empty.fields.iter().map(VertexAttribute::get_stride).sum::<usize>(),
5200            0
5201        );
5202
5203        // vec2<f32> + vec4<u8> = 8 + 4 = 12 bytes per vertex.
5204        let layout = VertexLayout {
5205            fields: vec![
5206                attr("vAttrXY", VertexAttributeType::Float, 2),
5207                attr("vColor", VertexAttributeType::UnsignedByte, 4),
5208            ]
5209            .into(),
5210        };
5211        let total: usize = layout.fields.iter().map(VertexAttribute::get_stride).sum();
5212        assert_eq!(total, 12);
5213
5214        // Equality/Hash are structural, so an identical layout compares equal.
5215        assert_eq!(layout, layout.clone());
5216    }
5217
5218    #[test]
5219    fn index_buffer_format_gl_ids_are_distinct() {
5220        // A collision here would silently draw the wrong primitive.
5221        for (i, a) in ALL_INDEX_FORMATS.iter().enumerate() {
5222            for b in ALL_INDEX_FORMATS.iter().skip(i + 1) {
5223                assert_ne!(a.get_gl_id(), b.get_gl_id(), "{a:?} vs {b:?}");
5224            }
5225        }
5226        assert_eq!(IndexBufferFormat::Points.get_gl_id(), gl::POINTS);
5227        assert_eq!(
5228            IndexBufferFormat::TriangleStrip.get_gl_id(),
5229            gl::TRIANGLE_STRIP
5230        );
5231    }
5232
5233    // =====================================================================
5234    // Uniform / UniformType (NaN semantics feed GlShader::draw's dedupe)
5235    // =====================================================================
5236
5237    #[test]
5238    fn uniform_type_nan_never_equals_itself_so_draw_always_reuploads_it() {
5239        // GlShader::draw skips re-setting a uniform when
5240        // `current_uniforms[i] != Some(uniform.uniform_type)`. With a NaN payload
5241        // that comparison is ALWAYS unequal, so a NaN uniform is re-uploaded on
5242        // every buffer -- correct (never stale), just not deduped. Pin it.
5243        let nan = UniformType::Float(f32::NAN);
5244        assert_ne!(nan, UniformType::Float(f32::NAN));
5245        assert_ne!(Some(nan), Some(nan));
5246
5247        let nan_vec = UniformType::FloatVec4([f32::NAN, 0.0, 0.0, 0.0]);
5248        assert_ne!(nan_vec, nan_vec);
5249
5250        // The flip side: +0.0 and -0.0 compare EQUAL, so a -0.0 -> +0.0 change is
5251        // deduped away and never re-uploaded. Harmless for a uniform, but pinned
5252        // so a change in semantics is visible.
5253        assert_eq!(UniformType::Float(0.0), UniformType::Float(-0.0));
5254
5255        // Integer uniforms have no such wrinkle: they dedupe exactly.
5256        assert_eq!(UniformType::Int(i32::MIN), UniformType::Int(i32::MIN));
5257        assert_ne!(UniformType::Int(0), UniformType::UnsignedInt(0));
5258    }
5259
5260    #[test]
5261    fn uniform_type_matrix_transpose_flag_is_part_of_its_identity() {
5262        let m = [0.0f32; 4];
5263        assert_ne!(
5264            UniformType::Matrix2 {
5265                transpose: false,
5266                matrix: m
5267            },
5268            UniformType::Matrix2 {
5269                transpose: true,
5270                matrix: m
5271            },
5272        );
5273        // Same payload, different arity -> different uniforms.
5274        assert_ne!(
5275            UniformType::FloatVec2([1.0, 2.0]),
5276            UniformType::IntVec2([1, 2])
5277        );
5278    }
5279
5280    #[test]
5281    fn uniform_create_preserves_empty_unicode_and_huge_names() {
5282        let huge = "n".repeat(100_000);
5283        for name in ["", "u_color", "\u{1F600}", "e\u{0301}", huge.as_str()] {
5284            let u = Uniform::create(name.to_string(), UniformType::Int(0));
5285            assert_eq!(u.uniform_name.as_str(), name);
5286        }
5287    }
5288
5289    // =====================================================================
5290    // GlShader -- the no-shader-compiler error path
5291    // =====================================================================
5292
5293    #[test]
5294    fn gl_shader_new_reports_no_shader_compiler_instead_of_panicking() {
5295        // A driver that reports no shader compiler (GL_SHADER_COMPILER == FALSE)
5296        // must produce a clean Err -- for ANY source, including empty, garbage,
5297        // unicode and very large ones.
5298        let ctx = null_ctx();
5299        let huge = "x".repeat(200_000);
5300        for (vert, frag) in [
5301            ("", ""),
5302            ("   \t\n  ", "\n\n"),
5303            ("not glsl at all ;;;{{{", "\u{1F600}"),
5304            ("void main(){}", "void main(){}"),
5305            (huge.as_str(), huge.as_str()),
5306        ] {
5307            let err = GlShader::new(&ctx, vert, frag).unwrap_err();
5308            assert_eq!(err, GlShaderCreateError::NoShaderCompiler);
5309        }
5310    }
5311
5312    #[test]
5313    fn shader_error_types_format_without_panicking_on_unicode_and_extremes() {
5314        let vert = VertexShaderCompileError {
5315            error_id: i32::MIN,
5316            info_log: AzString::from("\u{1F600} log".to_string()),
5317        };
5318        let frag = FragmentShaderCompileError {
5319            error_id: i32::MAX,
5320            info_log: AzString::from(String::new()),
5321        };
5322        let link = GlShaderLinkError {
5323            error_id: -1,
5324            info_log: AzString::from("multi\nline\0log".to_string()),
5325        };
5326
5327        assert!(alloc::format!("{vert}").contains("-2147483648"));
5328        assert!(alloc::format!("{vert}").contains("\u{1F600}"));
5329        assert!(alloc::format!("{frag}").contains("2147483647"));
5330
5331        let compile = GlShaderCompileError::Vertex(vert);
5332        assert!(alloc::format!("{compile}").contains("Failed to compile vertex shader"));
5333        // Debug delegates to Display -- must agree, and must not recurse.
5334        assert_eq!(alloc::format!("{compile:?}"), alloc::format!("{compile}"));
5335
5336        let frag_err = GlShaderCompileError::Fragment(frag);
5337        assert!(alloc::format!("{frag_err}").contains("Failed to compile fragment shader"));
5338
5339        let create = GlShaderCreateError::Link(link);
5340        assert!(alloc::format!("{create}").contains("Shader linking error"));
5341        assert_eq!(alloc::format!("{create:?}"), alloc::format!("{create}"));
5342        assert!(alloc::format!("{}", GlShaderCreateError::NoShaderCompiler)
5343            .contains("doesn't include a shader compiler"));
5344    }
5345
5346    // =====================================================================
5347    // Texture -- getters, refcounting, and the degenerate-driver paths
5348    // =====================================================================
5349
5350    #[test]
5351    fn texture_descriptor_mirrors_the_texture_including_extreme_sizes() {
5352        let tex = test_texture(42, 640, 480);
5353        let d = tex.get_descriptor();
5354        assert_eq!(d.width, 640);
5355        assert_eq!(d.height, 480);
5356        assert_eq!(d.format, RawImageFormat::RGBA8);
5357        assert_eq!(d.offset, 0);
5358        assert!(!d.flags.is_opaque);
5359        assert!(!d.flags.allow_mipmaps, "textures map 1:1, never mipmapped");
5360
5361        // Zero-size and u32::MAX-size must not panic or wrap in the descriptor.
5362        let zero = test_texture(1, 0, 0);
5363        assert_eq!(zero.get_descriptor().width, 0);
5364        assert_eq!(zero.get_descriptor().height, 0);
5365
5366        let huge = test_texture(1, u32::MAX, u32::MAX);
5367        assert_eq!(huge.get_descriptor().width, u32::MAX as usize);
5368        assert_eq!(huge.get_descriptor().height, u32::MAX as usize);
5369
5370        // is_opaque must be carried through from the flags, not hardcoded.
5371        let opaque = Texture::create(
5372            7,
5373            TextureFlags {
5374                is_opaque: true,
5375                is_video_texture: true,
5376            },
5377            PhysicalSizeU32 {
5378                width: 2,
5379                height: 2,
5380            },
5381            ColorU {
5382                r: 0,
5383                g: 0,
5384                b: 0,
5385                a: 0,
5386            },
5387            null_ctx(),
5388            RawImageFormat::BGRA8,
5389        );
5390        assert!(opaque.get_descriptor().flags.is_opaque);
5391        assert_eq!(opaque.get_descriptor().format, RawImageFormat::BGRA8);
5392    }
5393
5394    #[test]
5395    fn texture_clone_and_drop_share_one_refcount_without_double_freeing() {
5396        // Texture is refcounted through a raw Box<AtomicUsize>; a miscount here
5397        // is a double-free (or a leaked GL texture). Exercise fan-out + drop.
5398        let tex = test_texture(9, 4, 4);
5399        let clones: Vec<Texture> = (0..16).map(|_| tex.clone()).collect();
5400        for c in &clones {
5401            assert_eq!(c.texture_id, 9);
5402            assert_eq!(c.size, tex.size);
5403            assert_eq!(c.format, tex.format);
5404            assert!(c.run_destructor);
5405        }
5406        // Equality/Hash are by texture id.
5407        assert_eq!(clones[0], tex);
5408        assert_eq!(clones[0], clones[1]);
5409        assert_ne!(tex, test_texture(10, 4, 4));
5410
5411        drop(clones); // 16 drops...
5412        drop(tex); // ...then the last one actually frees.
5413    }
5414
5415    #[test]
5416    fn texture_display_and_debug_render_id_and_size() {
5417        let tex = test_texture(3, 16, 32);
5418        assert_eq!(alloc::format!("{tex}"), "Texture { id: 3, 16x32 }");
5419        // Debug delegates to Display.
5420        assert_eq!(alloc::format!("{tex:?}"), alloc::format!("{tex}"));
5421    }
5422
5423    #[test]
5424    fn texture_paint_stroke_is_a_noop_when_gl_is_unusable() {
5425        // Documented: "No-op if the GL context is unusable". With no brush shader
5426        // (program id 0) this must bail out before touching GL -- including for
5427        // NaN / infinite / negative radii and coordinates, which must not produce
5428        // a huge dab loop. (`!(radius > 0.0)` is written that way precisely so it
5429        // also rejects NaN.)
5430        let mut tex = test_texture(5, 8, 8);
5431        assert_eq!(tex.gl_context.get_brush_shader(), 0);
5432
5433        for radius in [1.0, 0.0, -1.0, f32::NAN, f32::INFINITY, f32::MAX] {
5434            let mut brush = Brush::new(
5435                ColorU {
5436                    r: 255,
5437                    g: 0,
5438                    b: 0,
5439                    a: 255,
5440                },
5441                radius,
5442            );
5443            brush.hardness = f32::NAN;
5444            brush.flow = f32::INFINITY;
5445            brush.spacing = 0.0; // would be a divide-by-zero step without the .max(0.01)
5446            tex.paint_stroke(f32::NAN, f32::NEG_INFINITY, f32::MAX, -0.0, brush);
5447            tex.paint_dot(f32::NAN, f32::NAN, brush);
5448        }
5449
5450        // A zero-sized texture is the other early-out (tw/th <= 0).
5451        let mut zero = test_texture(6, 0, 0);
5452        zero.paint_dot(
5453            0.0,
5454            0.0,
5455            Brush::new(
5456                ColorU {
5457                    r: 1,
5458                    g: 1,
5459                    b: 1,
5460                    a: 1,
5461                },
5462                4.0,
5463            ),
5464        );
5465    }
5466
5467    #[test]
5468    fn texture_copy_to_raw_image_returns_a_null_image_on_every_degenerate_input() {
5469        // The `w <= 0 || h <= 0` guard is load-bearing: size is a u32 but is cast
5470        // to i32 for glReadPixels, so a width >= 2^31 goes NEGATIVE. Without the
5471        // guard that reaches gl-context-loader's
5472        //   `vec![0; width as usize * height as usize * bit_depth]`
5473        // which sign-extends the negative width to ~usize::MAX -> overflow panic
5474        // (debug) / exabyte allocation (release). Prove the guard holds.
5475        let is_null_image = |img: &RawImage| img.width == 0 && img.height == 0;
5476
5477        // texture id 0 -> null image
5478        assert!(is_null_image(&test_texture(0, 16, 16).copy_to_raw_image()));
5479        // zero size -> null image
5480        assert!(is_null_image(&test_texture(1, 0, 0).copy_to_raw_image()));
5481        assert!(is_null_image(&test_texture(1, 16, 0).copy_to_raw_image()));
5482        assert!(is_null_image(&test_texture(1, 0, 16).copy_to_raw_image()));
5483
5484        // u32::MAX / 2^31 both cast to a NEGATIVE i32 and MUST be caught by the guard.
5485        assert!(is_null_image(
5486            &test_texture(1, u32::MAX, u32::MAX).copy_to_raw_image()
5487        ));
5488        assert!(is_null_image(
5489            &test_texture(1, 1 << 31, 1 << 31).copy_to_raw_image()
5490        ));
5491
5492        // Valid id + valid size, but the driver hands back no framebuffer
5493        // (`fbo == 0`) -> still a clean null image, no unwrap panic.
5494        assert!(is_null_image(&test_texture(1, 4, 4).copy_to_raw_image()));
5495    }
5496
5497    #[test]
5498    #[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
5499    fn texture_clear_panics_when_the_driver_allocates_no_framebuffer() {
5500        // DOCUMENTED panic: "Panics if no framebuffer/depthbuffer was allocated
5501        // (the GL object lists are empty)." A driver that generates nothing makes
5502        // gen_framebuffers(1) return an EMPTY list, so `.get(0).unwrap()` panics.
5503        // Pinned as documented behavior -- note that the sibling paths
5504        // (paint_stroke / copy_to_raw_image) degrade gracefully instead.
5505        test_texture(1, 4, 4).clear();
5506    }
5507
5508    // =====================================================================
5509    // VertexArrayObject / VertexBuffer
5510    // =====================================================================
5511
5512    #[test]
5513    fn vertex_array_object_clone_and_drop_share_one_refcount() {
5514        let layout = VertexLayout {
5515            fields: VertexAttributeVec::from_const_slice(&[]),
5516        };
5517        let vao = VertexArrayObject::new(layout, 77, null_ctx());
5518        assert_eq!(vao.vao_id, 77);
5519        assert!(vao.run_destructor);
5520
5521        let clones: Vec<VertexArrayObject> = (0..8).map(|_| vao.clone()).collect();
5522        assert!(clones.iter().all(|c| c.vao_id == 77));
5523        assert_eq!(clones[0], vao);
5524        drop(clones);
5525        drop(vao);
5526    }
5527
5528    #[test]
5529    fn vertex_buffer_new_raw_keeps_its_fields_and_refcounts_its_clones() {
5530        let vao = VertexArrayObject::new(
5531            VertexLayout {
5532                fields: VertexAttributeVec::from_const_slice(&[]),
5533            },
5534            1,
5535            null_ctx(),
5536        );
5537        let vb = VertexBuffer::new_raw(11, 300, vao, 22, 40, IndexBufferFormat::TriangleStrip);
5538
5539        assert_eq!(vb.vertex_buffer_id, 11);
5540        assert_eq!(vb.vertex_buffer_len, 300);
5541        assert_eq!(vb.index_buffer_id, 22);
5542        assert_eq!(vb.index_buffer_len, 40);
5543        assert_eq!(vb.index_buffer_format, IndexBufferFormat::TriangleStrip);
5544        assert_eq!(
5545            alloc::format!("{vb}"),
5546            "VertexBuffer { buffer: 11 (length: 300) }})"
5547        );
5548
5549        // Eq/Hash are by vertex_buffer_id.
5550        let clones: Vec<VertexBuffer> = (0..8).map(|_| vb.clone()).collect();
5551        assert_eq!(clones[0], vb);
5552        drop(clones);
5553        drop(vb);
5554
5555        // A zero-length buffer is degenerate but legal.
5556        let empty_vao = VertexArrayObject::new(
5557            VertexLayout {
5558                fields: VertexAttributeVec::from_const_slice(&[]),
5559            },
5560            0,
5561            null_ctx(),
5562        );
5563        let empty = VertexBuffer::new_raw(0, 0, empty_vao, 0, 0, IndexBufferFormat::Points);
5564        assert_eq!(empty.vertex_buffer_len, 0);
5565    }
5566
5567    #[allow(dead_code)] // the field only exists to give the vertex a realistic size/layout
5568    struct TestVertex {
5569        _xy: [f32; 2],
5570    }
5571
5572    impl VertexLayoutDescription for TestVertex {
5573        fn get_description() -> VertexLayout {
5574            VertexLayout {
5575                fields: vec![VertexAttribute {
5576                    va_name: AzString::from_const_str("vAttrXY"),
5577                    layout_location: OptionUsize::None,
5578                    attribute_type: VertexAttributeType::Float,
5579                    item_count: 2,
5580                }]
5581                .into(),
5582            }
5583        }
5584    }
5585
5586    #[test]
5587    #[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
5588    fn vertex_buffer_new_panics_when_the_driver_allocates_no_vao() {
5589        // DOCUMENTED panic: "Panics if the GL driver failed to create the
5590        // vertex-array/buffer objects (the returned id lists are empty)."
5591        let verts = [TestVertex { _xy: [0.0, 0.0] }];
5592        let _ = VertexBuffer::new(
5593            null_ctx(),
5594            0,
5595            &verts[..],
5596            &[0u32],
5597            IndexBufferFormat::Triangles,
5598        );
5599    }
5600
5601    // =====================================================================
5602    // The process-global GL texture table.
5603    //
5604    // ACTIVE_GL_TEXTURES is a `static mut` with no lock (GL is single-threaded
5605    // by design), and cargo test runs #[test]s on parallel threads. So ALL of
5606    // its coverage lives in this ONE test -- splitting it up would race the
5607    // table against itself. No other test in this crate touches it.
5608    // =====================================================================
5609
5610    #[test]
5611    fn gl_texture_cache_insert_lookup_and_eviction_lifecycle() {
5612        let doc = DocumentId {
5613            namespace_id: crate::resources::IdNamespace(7),
5614            id: 1,
5615        };
5616        let other_doc = DocumentId {
5617            namespace_id: crate::resources::IdNamespace(7),
5618            id: 2,
5619        };
5620
5621        // --- Empty / uninitialized table: every accessor must degrade, not panic.
5622        gl_textures_clear_opengl_cache();
5623        let stale = ExternalImageId { inner: u64::MAX };
5624        assert!(get_opengl_texture(&stale).is_none());
5625        assert!(
5626            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(0), &stale).is_none()
5627        );
5628        gl_textures_remove_epochs_from_pipeline(&doc, Epoch::from(0));
5629        gl_textures_remove_active_pipeline(&doc);
5630        gl_textures_clear_opengl_cache(); // idempotent
5631
5632        // --- Insert into an UNINITIALIZED table.
5633        // NOTE: the doc comment on insert_into_active_gl_textures claims it
5634        // "Panics if the global active-GL-texture table has not been
5635        // initialized" -- it does not; it initializes the table itself. The doc
5636        // is stale (see the report). Pin the real behavior.
5637        let id5 = insert_into_active_gl_textures(doc, Epoch::from(5), test_texture(11, 4, 8));
5638        let id7 = insert_into_active_gl_textures(doc, Epoch::from(7), test_texture(22, 16, 32));
5639        assert_ne!(id5, id7, "each insert must mint a unique ExternalImageId");
5640
5641        // --- Lookup: id -> (texture id, (w, h) as f32).
5642        assert_eq!(get_opengl_texture(&id5), Some((11, (4.0, 8.0))));
5643        assert_eq!(get_opengl_texture(&id7), Some((22, (16.0, 32.0))));
5644        assert!(get_opengl_texture(&stale).is_none());
5645
5646        // A u32::MAX-sized texture goes through a lossy u32 -> f32 cast:
5647        // u32::MAX (2^32 - 1) has no exact f32, so it rounds UP to 2^32.
5648        let id_huge =
5649            insert_into_active_gl_textures(doc, Epoch::from(5), test_texture(33, u32::MAX, 1));
5650        assert_eq!(get_opengl_texture(&id_huge), Some((33, (4_294_967_296.0, 1.0))));
5651
5652        // --- Epoch eviction is STRICTLY older-than: epoch 5 goes, epoch 7 stays.
5653        gl_textures_remove_epochs_from_pipeline(&doc, Epoch::from(7));
5654        assert!(get_opengl_texture(&id5).is_none(), "epoch 5 < 7 must be evicted");
5655        assert!(get_opengl_texture(&id_huge).is_none(), "epoch 5 < 7 must be evicted");
5656        assert_eq!(
5657            get_opengl_texture(&id7),
5658            Some((22, (16.0, 32.0))),
5659            "epoch 7 is NOT < 7, so it must survive"
5660        );
5661
5662        // Evicting for an unknown document must be a no-op, not a panic.
5663        gl_textures_remove_epochs_from_pipeline(&other_doc, Epoch::from(u32::MAX));
5664        assert_eq!(get_opengl_texture(&id7), Some((22, (16.0, 32.0))));
5665
5666        // --- remove_single: Some(()) when the (doc, epoch) path exists...
5667        assert_eq!(
5668            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(7), &id7),
5669            Some(())
5670        );
5671        assert!(get_opengl_texture(&id7).is_none());
5672        // ...including a second removal of an already-gone image (the path is
5673        // still there, so it reports Some(()) rather than None)...
5674        assert_eq!(
5675            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(7), &id7),
5676            Some(())
5677        );
5678        // ...but None when the document or the epoch is unknown.
5679        assert!(
5680            remove_single_texture_from_active_gl_textures(&other_doc, &Epoch::from(7), &id7)
5681                .is_none()
5682        );
5683        assert!(remove_single_texture_from_active_gl_textures(
5684            &doc,
5685            &Epoch::from(u32::MAX),
5686            &id7
5687        )
5688        .is_none());
5689
5690        // --- remove_active_pipeline drops the whole document.
5691        let id_a = insert_into_active_gl_textures(doc, Epoch::from(1), test_texture(44, 2, 2));
5692        let id_b = insert_into_active_gl_textures(other_doc, Epoch::from(1), test_texture(55, 2, 2));
5693        assert!(get_opengl_texture(&id_a).is_some());
5694        gl_textures_remove_active_pipeline(&doc);
5695        assert!(get_opengl_texture(&id_a).is_none(), "doc was removed");
5696        assert!(
5697            get_opengl_texture(&id_b).is_some(),
5698            "other_doc must be untouched"
5699        );
5700
5701        // --- clear drops everything.
5702        gl_textures_clear_opengl_cache();
5703        assert!(get_opengl_texture(&id_b).is_none());
5704
5705        // Leave the table clean for anything that runs after us.
5706        gl_textures_clear_opengl_cache();
5707    }
5708}