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        // gl-context-loader's own read_pixels sizes the buffer as
1495        // width*height*bytes_per_component and OMITS the format's channel count, so a
1496        // 2x3 RGBA/UNSIGNED_BYTE read allocates 6 bytes instead of 24 — a heap overflow
1497        // once a real driver writes the pixels. Size it correctly from format + type and
1498        // read into our own buffer. (Raw GL enum values so this doesn't depend on which
1499        // constants the gl module happens to re-export.)
1500        let channels: usize = match format {
1501            // RED, ALPHA, LUMINANCE, DEPTH_COMPONENT, STENCIL_INDEX, RED_INTEGER
1502            0x1903 | 0x1906 | 0x1909 | 0x1902 | 0x1901 | 0x8D94 => 1,
1503            // RG, LUMINANCE_ALPHA, RG_INTEGER, DEPTH_STENCIL
1504            0x8227 | 0x190A | 0x8228 | 0x84F9 => 2,
1505            // RGB, BGR, RGB_INTEGER
1506            0x1907 | 0x80E0 | 0x8D98 => 3,
1507            // RGBA, BGRA, RGBA_INTEGER, and a conservative default
1508            _ => 4,
1509        };
1510        let bytes_per_component: usize = match pixel_type {
1511            0x1400 | 0x1401 => 1,          // BYTE, UNSIGNED_BYTE
1512            0x1402 | 0x1403 | 0x140B => 2, // SHORT, UNSIGNED_SHORT, HALF_FLOAT
1513            _ => 4,                        // INT, UNSIGNED_INT, FLOAT, and default
1514        };
1515        // width/height are clamped to >= 0 before the cast, so no sign is lost.
1516        #[allow(clippy::cast_sign_loss)]
1517        let len = (width.max(0) as usize)
1518            .saturating_mul(height.max(0) as usize)
1519            .saturating_mul(channels)
1520            .saturating_mul(bytes_per_component);
1521        let mut buf = vec![0u8; len];
1522        self.get().read_pixels_into_buffer(
1523            x,
1524            y,
1525            width,
1526            height,
1527            format,
1528            pixel_type,
1529            buf.as_mut_slice(),
1530        );
1531        buf.into()
1532    }
1533    pub fn read_pixels_into_pbo(
1534        &self,
1535        x: GLint,
1536        y: GLint,
1537        width: GLsizei,
1538        height: GLsizei,
1539        format: GLenum,
1540        pixel_type: GLenum,
1541    ) {
1542        unsafe {
1543            self.get()
1544                .read_pixels_into_pbo(x, y, width, height, format, pixel_type);
1545        }
1546    }
1547    pub fn sample_coverage(&self, value: GLclampf, invert: bool) {
1548        self.get().sample_coverage(value, invert);
1549    }
1550    pub fn polygon_offset(&self, factor: GLfloat, units: GLfloat) {
1551        self.get().polygon_offset(factor, units);
1552    }
1553    pub fn pixel_store_i(&self, name: GLenum, param: GLint) {
1554        self.get().pixel_store_i(name, param);
1555    }
1556    #[must_use] pub fn gen_buffers(&self, n: GLsizei) -> GLuintVec {
1557        self.get().gen_buffers(n).into()
1558    }
1559    #[must_use] pub fn gen_renderbuffers(&self, n: GLsizei) -> GLuintVec {
1560        self.get().gen_renderbuffers(n).into()
1561    }
1562    #[must_use] pub fn gen_framebuffers(&self, n: GLsizei) -> GLuintVec {
1563        self.get().gen_framebuffers(n).into()
1564    }
1565    #[must_use] pub fn gen_textures(&self, n: GLsizei) -> GLuintVec {
1566        self.get().gen_textures(n).into()
1567    }
1568    #[must_use] pub fn gen_vertex_arrays(&self, n: GLsizei) -> GLuintVec {
1569        self.get().gen_vertex_arrays(n).into()
1570    }
1571    #[must_use] pub fn gen_queries(&self, n: GLsizei) -> GLuintVec {
1572        self.get().gen_queries(n).into()
1573    }
1574    pub fn begin_query(&self, target: GLenum, id: GLuint) {
1575        self.get().begin_query(target, id);
1576    }
1577    pub fn end_query(&self, target: GLenum) {
1578        self.get().end_query(target);
1579    }
1580    pub fn query_counter(&self, id: GLuint, target: GLenum) {
1581        self.get().query_counter(id, target);
1582    }
1583    #[must_use] pub fn get_query_object_iv(&self, id: GLuint, pname: GLenum) -> i32 {
1584        self.get().get_query_object_iv(id, pname)
1585    }
1586    #[must_use] pub fn get_query_object_uiv(&self, id: GLuint, pname: GLenum) -> u32 {
1587        self.get().get_query_object_uiv(id, pname)
1588    }
1589    #[must_use] pub fn get_query_object_i64v(&self, id: GLuint, pname: GLenum) -> i64 {
1590        self.get().get_query_object_i64v(id, pname)
1591    }
1592    #[must_use] pub fn get_query_object_ui64v(&self, id: GLuint, pname: GLenum) -> u64 {
1593        self.get().get_query_object_ui64v(id, pname)
1594    }
1595    pub fn delete_queries(&self, queries: GLuintVecRef) {
1596        self.get().delete_queries(queries.as_slice());
1597    }
1598    pub fn delete_vertex_arrays(&self, vertex_arrays: GLuintVecRef) {
1599        self.get().delete_vertex_arrays(vertex_arrays.as_slice());
1600    }
1601    pub fn delete_buffers(&self, buffers: GLuintVecRef) {
1602        self.get().delete_buffers(buffers.as_slice());
1603    }
1604    pub fn delete_renderbuffers(&self, renderbuffers: GLuintVecRef) {
1605        self.get().delete_renderbuffers(renderbuffers.as_slice());
1606    }
1607    pub fn delete_framebuffers(&self, framebuffers: GLuintVecRef) {
1608        self.get().delete_framebuffers(framebuffers.as_slice());
1609    }
1610    pub fn delete_textures(&self, textures: GLuintVecRef) {
1611        self.get().delete_textures(textures.as_slice());
1612    }
1613    pub fn framebuffer_renderbuffer(
1614        &self,
1615        target: GLenum,
1616        attachment: GLenum,
1617        renderbuffertarget: GLenum,
1618        renderbuffer: GLuint,
1619    ) {
1620        self.get()
1621            .framebuffer_renderbuffer(target, attachment, renderbuffertarget, renderbuffer);
1622    }
1623    pub fn renderbuffer_storage(
1624        &self,
1625        target: GLenum,
1626        internalformat: GLenum,
1627        width: GLsizei,
1628        height: GLsizei,
1629    ) {
1630        self.get()
1631            .renderbuffer_storage(target, internalformat, width, height);
1632    }
1633    pub fn depth_func(&self, func: GLenum) {
1634        self.get().depth_func(func);
1635    }
1636    pub fn active_texture(&self, texture: GLenum) {
1637        self.get().active_texture(texture);
1638    }
1639    pub fn attach_shader(&self, program: GLuint, shader: GLuint) {
1640        self.get().attach_shader(program, shader);
1641    }
1642    pub fn bind_attrib_location(&self, program: GLuint, index: GLuint, name: &str) {
1643        self.get()
1644            .bind_attrib_location(program, index, name);
1645    }
1646    pub fn get_uniform_iv(&self, program: GLuint, location: GLint, mut result: GLintVecRefMut) {
1647        unsafe {
1648            self.get()
1649                .get_uniform_iv(program, location, result.as_mut_slice());
1650        }
1651    }
1652    pub fn get_uniform_fv(&self, program: GLuint, location: GLint, mut result: GLfloatVecRefMut) {
1653        unsafe {
1654            self.get()
1655                .get_uniform_fv(program, location, result.as_mut_slice());
1656        }
1657    }
1658    #[must_use] pub fn get_uniform_block_index(&self, program: GLuint, name: &str) -> GLuint {
1659        self.get().get_uniform_block_index(program, name)
1660    }
1661    #[must_use] pub fn get_uniform_indices(&self, program: GLuint, names: RefstrVecRef) -> GLuintVec {
1662        let names_vec = names
1663            .as_slice()
1664            .iter()
1665            .map(Refstr::as_str)
1666            .collect::<Vec<_>>();
1667        self.get().get_uniform_indices(program, &names_vec).into()
1668    }
1669    pub fn bind_buffer_base(&self, target: GLenum, index: GLuint, buffer: GLuint) {
1670        self.get().bind_buffer_base(target, index, buffer);
1671    }
1672    pub fn bind_buffer_range(
1673        &self,
1674        target: GLenum,
1675        index: GLuint,
1676        buffer: GLuint,
1677        offset: GLintptr,
1678        size: GLsizeiptr,
1679    ) {
1680        self.get()
1681            .bind_buffer_range(target, index, buffer, offset, size);
1682    }
1683    pub fn uniform_block_binding(
1684        &self,
1685        program: GLuint,
1686        uniform_block_index: GLuint,
1687        uniform_block_binding: GLuint,
1688    ) {
1689        self.get()
1690            .uniform_block_binding(program, uniform_block_index, uniform_block_binding);
1691    }
1692    pub fn bind_buffer(&self, target: GLenum, buffer: GLuint) {
1693        self.get().bind_buffer(target, buffer);
1694    }
1695    pub fn bind_vertex_array(&self, vao: GLuint) {
1696        self.get().bind_vertex_array(vao);
1697    }
1698    pub fn bind_renderbuffer(&self, target: GLenum, renderbuffer: GLuint) {
1699        self.get().bind_renderbuffer(target, renderbuffer);
1700    }
1701    pub fn bind_framebuffer(&self, target: GLenum, framebuffer: GLuint) {
1702        self.get().bind_framebuffer(target, framebuffer);
1703    }
1704    pub fn bind_texture(&self, target: GLenum, texture: GLuint) {
1705        self.get().bind_texture(target, texture);
1706    }
1707    pub fn draw_buffers(&self, bufs: GLenumVecRef) {
1708        self.get().draw_buffers(bufs.as_slice());
1709    }
1710    pub fn tex_image_2d(
1711        &self,
1712        target: GLenum,
1713        level: GLint,
1714        internal_format: GLint,
1715        width: GLsizei,
1716        height: GLsizei,
1717        border: GLint,
1718        format: GLenum,
1719        ty: GLenum,
1720        opt_data: OptionU8VecRef,
1721    ) {
1722        let opt_data = opt_data.as_option();
1723        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
1724        self.get().tex_image_2d(
1725            target,
1726            level,
1727            internal_format,
1728            width,
1729            height,
1730            border,
1731            format,
1732            ty,
1733            opt_data,
1734        );
1735    }
1736    pub fn compressed_tex_image_2d(
1737        &self,
1738        target: GLenum,
1739        level: GLint,
1740        internal_format: GLenum,
1741        width: GLsizei,
1742        height: GLsizei,
1743        border: GLint,
1744        data: U8VecRef,
1745    ) {
1746        self.get().compressed_tex_image_2d(
1747            target,
1748            level,
1749            internal_format,
1750            width,
1751            height,
1752            border,
1753            data.as_slice(),
1754        );
1755    }
1756    pub fn compressed_tex_sub_image_2d(
1757        &self,
1758        target: GLenum,
1759        level: GLint,
1760        xoffset: GLint,
1761        yoffset: GLint,
1762        width: GLsizei,
1763        height: GLsizei,
1764        format: GLenum,
1765        data: U8VecRef,
1766    ) {
1767        self.get().compressed_tex_sub_image_2d(
1768            target,
1769            level,
1770            xoffset,
1771            yoffset,
1772            width,
1773            height,
1774            format,
1775            data.as_slice(),
1776        );
1777    }
1778    pub fn tex_image_3d(
1779        &self,
1780        target: GLenum,
1781        level: GLint,
1782        internal_format: GLint,
1783        width: GLsizei,
1784        height: GLsizei,
1785        depth: GLsizei,
1786        border: GLint,
1787        format: GLenum,
1788        ty: GLenum,
1789        opt_data: OptionU8VecRef,
1790    ) {
1791        let opt_data = opt_data.as_option();
1792        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
1793        self.get().tex_image_3d(
1794            target,
1795            level,
1796            internal_format,
1797            width,
1798            height,
1799            depth,
1800            border,
1801            format,
1802            ty,
1803            opt_data,
1804        );
1805    }
1806    pub fn copy_tex_image_2d(
1807        &self,
1808        target: GLenum,
1809        level: GLint,
1810        internal_format: GLenum,
1811        x: GLint,
1812        y: GLint,
1813        width: GLsizei,
1814        height: GLsizei,
1815        border: GLint,
1816    ) {
1817        self.get()
1818            .copy_tex_image_2d(target, level, internal_format, x, y, width, height, border);
1819    }
1820    pub fn copy_tex_sub_image_2d(
1821        &self,
1822        target: GLenum,
1823        level: GLint,
1824        xoffset: GLint,
1825        yoffset: GLint,
1826        x: GLint,
1827        y: GLint,
1828        width: GLsizei,
1829        height: GLsizei,
1830    ) {
1831        self.get()
1832            .copy_tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height);
1833    }
1834    pub fn copy_tex_sub_image_3d(
1835        &self,
1836        target: GLenum,
1837        level: GLint,
1838        xoffset: GLint,
1839        yoffset: GLint,
1840        zoffset: GLint,
1841        x: GLint,
1842        y: GLint,
1843        width: GLsizei,
1844        height: GLsizei,
1845    ) {
1846        self.get().copy_tex_sub_image_3d(
1847            target, level, xoffset, yoffset, zoffset, x, y, width, height,
1848        );
1849    }
1850    pub fn tex_sub_image_2d(
1851        &self,
1852        target: GLenum,
1853        level: GLint,
1854        xoffset: GLint,
1855        yoffset: GLint,
1856        width: GLsizei,
1857        height: GLsizei,
1858        format: GLenum,
1859        ty: GLenum,
1860        data: U8VecRef,
1861    ) {
1862        self.get().tex_sub_image_2d(
1863            target,
1864            level,
1865            xoffset,
1866            yoffset,
1867            width,
1868            height,
1869            format,
1870            ty,
1871            data.as_slice(),
1872        );
1873    }
1874    pub fn tex_sub_image_2d_pbo(
1875        &self,
1876        target: GLenum,
1877        level: GLint,
1878        xoffset: GLint,
1879        yoffset: GLint,
1880        width: GLsizei,
1881        height: GLsizei,
1882        format: GLenum,
1883        ty: GLenum,
1884        offset: usize,
1885    ) {
1886        self.get().tex_sub_image_2d_pbo(
1887            target, level, xoffset, yoffset, width, height, format, ty, offset,
1888        );
1889    }
1890    pub fn tex_sub_image_3d(
1891        &self,
1892        target: GLenum,
1893        level: GLint,
1894        xoffset: GLint,
1895        yoffset: GLint,
1896        zoffset: GLint,
1897        width: GLsizei,
1898        height: GLsizei,
1899        depth: GLsizei,
1900        format: GLenum,
1901        ty: GLenum,
1902        data: U8VecRef,
1903    ) {
1904        self.get().tex_sub_image_3d(
1905            target,
1906            level,
1907            xoffset,
1908            yoffset,
1909            zoffset,
1910            width,
1911            height,
1912            depth,
1913            format,
1914            ty,
1915            data.as_slice(),
1916        );
1917    }
1918    pub fn tex_sub_image_3d_pbo(
1919        &self,
1920        target: GLenum,
1921        level: GLint,
1922        xoffset: GLint,
1923        yoffset: GLint,
1924        zoffset: GLint,
1925        width: GLsizei,
1926        height: GLsizei,
1927        depth: GLsizei,
1928        format: GLenum,
1929        ty: GLenum,
1930        offset: usize,
1931    ) {
1932        self.get().tex_sub_image_3d_pbo(
1933            target, level, xoffset, yoffset, zoffset, width, height, depth, format, ty, offset,
1934        );
1935    }
1936    pub fn tex_storage_2d(
1937        &self,
1938        target: GLenum,
1939        levels: GLint,
1940        internal_format: GLenum,
1941        width: GLsizei,
1942        height: GLsizei,
1943    ) {
1944        self.get()
1945            .tex_storage_2d(target, levels, internal_format, width, height);
1946    }
1947    pub fn tex_storage_3d(
1948        &self,
1949        target: GLenum,
1950        levels: GLint,
1951        internal_format: GLenum,
1952        width: GLsizei,
1953        height: GLsizei,
1954        depth: GLsizei,
1955    ) {
1956        self.get()
1957            .tex_storage_3d(target, levels, internal_format, width, height, depth);
1958    }
1959    pub fn get_tex_image_into_buffer(
1960        &self,
1961        target: GLenum,
1962        level: GLint,
1963        format: GLenum,
1964        ty: GLenum,
1965        mut output: U8VecRefMut,
1966    ) {
1967        self.get()
1968            .get_tex_image_into_buffer(target, level, format, ty, output.as_mut_slice());
1969    }
1970    pub fn copy_image_sub_data(
1971        &self,
1972        src_name: GLuint,
1973        src_target: GLenum,
1974        src_level: GLint,
1975        src_x: GLint,
1976        src_y: GLint,
1977        src_z: GLint,
1978        dst_name: GLuint,
1979        dst_target: GLenum,
1980        dst_level: GLint,
1981        dst_x: GLint,
1982        dst_y: GLint,
1983        dst_z: GLint,
1984        src_width: GLsizei,
1985        src_height: GLsizei,
1986        src_depth: GLsizei,
1987    ) {
1988        unsafe {
1989            self.get().copy_image_sub_data(
1990                src_name, src_target, src_level, src_x, src_y, src_z, dst_name, dst_target,
1991                dst_level, dst_x, dst_y, dst_z, src_width, src_height, src_depth,
1992            );
1993        }
1994    }
1995    pub fn invalidate_framebuffer(&self, target: GLenum, attachments: GLenumVecRef) {
1996        self.get()
1997            .invalidate_framebuffer(target, attachments.as_slice());
1998    }
1999    pub fn invalidate_sub_framebuffer(
2000        &self,
2001        target: GLenum,
2002        attachments: GLenumVecRef,
2003        xoffset: GLint,
2004        yoffset: GLint,
2005        width: GLsizei,
2006        height: GLsizei,
2007    ) {
2008        self.get().invalidate_sub_framebuffer(
2009            target,
2010            attachments.as_slice(),
2011            xoffset,
2012            yoffset,
2013            width,
2014            height,
2015        );
2016    }
2017    pub fn get_integer_v(&self, name: GLenum, mut result: GLintVecRefMut) {
2018        unsafe { self.get().get_integer_v(name, result.as_mut_slice()) }
2019    }
2020    pub fn get_integer_64v(&self, name: GLenum, mut result: GLint64VecRefMut) {
2021        unsafe { self.get().get_integer_64v(name, result.as_mut_slice()) }
2022    }
2023    pub fn get_integer_iv(&self, name: GLenum, index: GLuint, mut result: GLintVecRefMut) {
2024        unsafe {
2025            self.get()
2026                .get_integer_iv(name, index, result.as_mut_slice());
2027        }
2028    }
2029    pub fn get_integer_64iv(&self, name: GLenum, index: GLuint, mut result: GLint64VecRefMut) {
2030        unsafe {
2031            self.get()
2032                .get_integer_64iv(name, index, result.as_mut_slice());
2033        }
2034    }
2035    pub fn get_boolean_v(&self, name: GLenum, mut result: GLbooleanVecRefMut) {
2036        unsafe { self.get().get_boolean_v(name, result.as_mut_slice()) }
2037    }
2038    pub fn get_float_v(&self, name: GLenum, mut result: GLfloatVecRefMut) {
2039        unsafe { self.get().get_float_v(name, result.as_mut_slice()) }
2040    }
2041    #[must_use] pub fn get_framebuffer_attachment_parameter_iv(
2042        &self,
2043        target: GLenum,
2044        attachment: GLenum,
2045        pname: GLenum,
2046    ) -> GLint {
2047        self.get()
2048            .get_framebuffer_attachment_parameter_iv(target, attachment, pname)
2049    }
2050    #[must_use] pub fn get_renderbuffer_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint {
2051        self.get().get_renderbuffer_parameter_iv(target, pname)
2052    }
2053    #[must_use] pub fn get_tex_parameter_iv(&self, target: GLenum, name: GLenum) -> GLint {
2054        self.get().get_tex_parameter_iv(target, name)
2055    }
2056    #[must_use] pub fn get_tex_parameter_fv(&self, target: GLenum, name: GLenum) -> GLfloat {
2057        self.get().get_tex_parameter_fv(target, name)
2058    }
2059    pub fn tex_parameter_i(&self, target: GLenum, pname: GLenum, param: GLint) {
2060        self.get().tex_parameter_i(target, pname, param);
2061    }
2062    pub fn tex_parameter_f(&self, target: GLenum, pname: GLenum, param: GLfloat) {
2063        self.get().tex_parameter_f(target, pname, param);
2064    }
2065    pub fn framebuffer_texture_2d(
2066        &self,
2067        target: GLenum,
2068        attachment: GLenum,
2069        textarget: GLenum,
2070        texture: GLuint,
2071        level: GLint,
2072    ) {
2073        self.get()
2074            .framebuffer_texture_2d(target, attachment, textarget, texture, level);
2075    }
2076    pub fn framebuffer_texture_layer(
2077        &self,
2078        target: GLenum,
2079        attachment: GLenum,
2080        texture: GLuint,
2081        level: GLint,
2082        layer: GLint,
2083    ) {
2084        self.get()
2085            .framebuffer_texture_layer(target, attachment, texture, level, layer);
2086    }
2087    #[allow(clippy::similar_names)] // domain-standard coordinate/control-point names
2088    pub fn blit_framebuffer(
2089        &self,
2090        src_x0: GLint,
2091        src_y0: GLint,
2092        src_x1: GLint,
2093        src_y1: GLint,
2094        dst_x0: GLint,
2095        dst_y0: GLint,
2096        dst_x1: GLint,
2097        dst_y1: GLint,
2098        mask: GLbitfield,
2099        filter: GLenum,
2100    ) {
2101        self.get().blit_framebuffer(
2102            src_x0, src_y0, src_x1, src_y1, dst_x0, dst_y0, dst_x1, dst_y1, mask, filter,
2103        );
2104    }
2105    pub fn vertex_attrib_4f(&self, index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
2106        self.get().vertex_attrib_4f(index, x, y, z, w);
2107    }
2108    pub fn vertex_attrib_pointer_f32(
2109        &self,
2110        index: GLuint,
2111        size: GLint,
2112        normalized: bool,
2113        stride: GLsizei,
2114        offset: GLuint,
2115    ) {
2116        self.get()
2117            .vertex_attrib_pointer_f32(index, size, normalized, stride, offset);
2118    }
2119    pub fn vertex_attrib_pointer(
2120        &self,
2121        index: GLuint,
2122        size: GLint,
2123        type_: GLenum,
2124        normalized: bool,
2125        stride: GLsizei,
2126        offset: GLuint,
2127    ) {
2128        self.get()
2129            .vertex_attrib_pointer(index, size, type_, normalized, stride, offset);
2130    }
2131    pub fn vertex_attrib_i_pointer(
2132        &self,
2133        index: GLuint,
2134        size: GLint,
2135        type_: GLenum,
2136        stride: GLsizei,
2137        offset: GLuint,
2138    ) {
2139        self.get()
2140            .vertex_attrib_i_pointer(index, size, type_, stride, offset);
2141    }
2142    pub fn vertex_attrib_divisor(&self, index: GLuint, divisor: GLuint) {
2143        self.get().vertex_attrib_divisor(index, divisor);
2144    }
2145    pub fn viewport(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
2146        self.get().viewport(x, y, width, height);
2147    }
2148    pub fn scissor(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
2149        self.get().scissor(x, y, width, height);
2150    }
2151    pub fn line_width(&self, width: GLfloat) {
2152        self.get().line_width(width);
2153    }
2154    pub fn use_program(&self, program: GLuint) {
2155        self.get().use_program(program);
2156    }
2157    pub fn validate_program(&self, program: GLuint) {
2158        self.get().validate_program(program);
2159    }
2160    pub fn draw_arrays(&self, mode: GLenum, first: GLint, count: GLsizei) {
2161        self.get().draw_arrays(mode, first, count);
2162    }
2163    pub fn draw_arrays_instanced(
2164        &self,
2165        mode: GLenum,
2166        first: GLint,
2167        count: GLsizei,
2168        primcount: GLsizei,
2169    ) {
2170        self.get()
2171            .draw_arrays_instanced(mode, first, count, primcount);
2172    }
2173    pub fn draw_elements(
2174        &self,
2175        mode: GLenum,
2176        count: GLsizei,
2177        element_type: GLenum,
2178        indices_offset: GLuint,
2179    ) {
2180        self.get()
2181            .draw_elements(mode, count, element_type, indices_offset);
2182    }
2183    pub fn draw_elements_instanced(
2184        &self,
2185        mode: GLenum,
2186        count: GLsizei,
2187        element_type: GLenum,
2188        indices_offset: GLuint,
2189        primcount: GLsizei,
2190    ) {
2191        self.get()
2192            .draw_elements_instanced(mode, count, element_type, indices_offset, primcount);
2193    }
2194    pub fn blend_color(&self, r: f32, g: f32, b: f32, a: f32) {
2195        self.get().blend_color(r, g, b, a);
2196    }
2197    pub fn blend_func(&self, sfactor: GLenum, dfactor: GLenum) {
2198        self.get().blend_func(sfactor, dfactor);
2199    }
2200    pub fn blend_func_separate(
2201        &self,
2202        src_rgb: GLenum,
2203        dest_rgb: GLenum,
2204        src_alpha: GLenum,
2205        dest_alpha: GLenum,
2206    ) {
2207        self.get()
2208            .blend_func_separate(src_rgb, dest_rgb, src_alpha, dest_alpha);
2209    }
2210    pub fn blend_equation(&self, mode: GLenum) {
2211        self.get().blend_equation(mode);
2212    }
2213    pub fn blend_equation_separate(&self, mode_rgb: GLenum, mode_alpha: GLenum) {
2214        self.get().blend_equation_separate(mode_rgb, mode_alpha);
2215    }
2216    // mirrors glColorMask(GLboolean, GLboolean, GLboolean, GLboolean) — the four
2217    // RGBA write-mask flags are the GL API, not a refactorable bool soup.
2218    #[allow(clippy::fn_params_excessive_bools)]
2219    pub fn color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
2220        self.get().color_mask(r, g, b, a);
2221    }
2222    pub fn cull_face(&self, mode: GLenum) {
2223        self.get().cull_face(mode);
2224    }
2225    pub fn front_face(&self, mode: GLenum) {
2226        self.get().front_face(mode);
2227    }
2228    pub fn enable(&self, cap: GLenum) {
2229        self.get().enable(cap);
2230    }
2231    pub fn disable(&self, cap: GLenum) {
2232        self.get().disable(cap);
2233    }
2234    pub fn hint(&self, param_name: GLenum, param_val: GLenum) {
2235        self.get().hint(param_name, param_val);
2236    }
2237    #[must_use] pub fn is_enabled(&self, cap: GLenum) -> GLboolean {
2238        self.get().is_enabled(cap)
2239    }
2240    #[must_use] pub fn is_shader(&self, shader: GLuint) -> GLboolean {
2241        self.get().is_shader(shader)
2242    }
2243    #[must_use] pub fn is_texture(&self, texture: GLenum) -> GLboolean {
2244        self.get().is_texture(texture)
2245    }
2246    #[must_use] pub fn is_framebuffer(&self, framebuffer: GLenum) -> GLboolean {
2247        self.get().is_framebuffer(framebuffer)
2248    }
2249    #[must_use] pub fn is_renderbuffer(&self, renderbuffer: GLenum) -> GLboolean {
2250        self.get().is_renderbuffer(renderbuffer)
2251    }
2252    #[must_use] pub fn check_frame_buffer_status(&self, target: GLenum) -> GLenum {
2253        self.get().check_frame_buffer_status(target)
2254    }
2255    pub fn enable_vertex_attrib_array(&self, index: GLuint) {
2256        self.get().enable_vertex_attrib_array(index);
2257    }
2258    pub fn disable_vertex_attrib_array(&self, index: GLuint) {
2259        self.get().disable_vertex_attrib_array(index);
2260    }
2261    pub fn uniform_1f(&self, location: GLint, v0: GLfloat) {
2262        self.get().uniform_1f(location, v0);
2263    }
2264    pub fn uniform_1fv(&self, location: GLint, values: F32VecRef) {
2265        self.get().uniform_1fv(location, values.as_slice());
2266    }
2267    pub fn uniform_1i(&self, location: GLint, v0: GLint) {
2268        self.get().uniform_1i(location, v0);
2269    }
2270    pub fn uniform_1iv(&self, location: GLint, values: I32VecRef) {
2271        self.get().uniform_1iv(location, values.as_slice());
2272    }
2273    pub fn uniform_1ui(&self, location: GLint, v0: GLuint) {
2274        self.get().uniform_1ui(location, v0);
2275    }
2276    pub fn uniform_2f(&self, location: GLint, v0: GLfloat, v1: GLfloat) {
2277        self.get().uniform_2f(location, v0, v1);
2278    }
2279    pub fn uniform_2fv(&self, location: GLint, values: F32VecRef) {
2280        self.get().uniform_2fv(location, values.as_slice());
2281    }
2282    pub fn uniform_2i(&self, location: GLint, v0: GLint, v1: GLint) {
2283        self.get().uniform_2i(location, v0, v1);
2284    }
2285    pub fn uniform_2iv(&self, location: GLint, values: I32VecRef) {
2286        self.get().uniform_2iv(location, values.as_slice());
2287    }
2288    pub fn uniform_2ui(&self, location: GLint, v0: GLuint, v1: GLuint) {
2289        self.get().uniform_2ui(location, v0, v1);
2290    }
2291    pub fn uniform_3f(&self, location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat) {
2292        self.get().uniform_3f(location, v0, v1, v2);
2293    }
2294    pub fn uniform_3fv(&self, location: GLint, values: F32VecRef) {
2295        self.get().uniform_3fv(location, values.as_slice());
2296    }
2297    pub fn uniform_3i(&self, location: GLint, v0: GLint, v1: GLint, v2: GLint) {
2298        self.get().uniform_3i(location, v0, v1, v2);
2299    }
2300    pub fn uniform_3iv(&self, location: GLint, values: I32VecRef) {
2301        self.get().uniform_3iv(location, values.as_slice());
2302    }
2303    pub fn uniform_3ui(&self, location: GLint, v0: GLuint, v1: GLuint, v2: GLuint) {
2304        self.get().uniform_3ui(location, v0, v1, v2);
2305    }
2306    pub fn uniform_4f(&self, location: GLint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
2307        self.get().uniform_4f(location, x, y, z, w);
2308    }
2309    pub fn uniform_4i(&self, location: GLint, x: GLint, y: GLint, z: GLint, w: GLint) {
2310        self.get().uniform_4i(location, x, y, z, w);
2311    }
2312    pub fn uniform_4iv(&self, location: GLint, values: I32VecRef) {
2313        self.get().uniform_4iv(location, values.as_slice());
2314    }
2315    pub fn uniform_4ui(&self, location: GLint, x: GLuint, y: GLuint, z: GLuint, w: GLuint) {
2316        self.get().uniform_4ui(location, x, y, z, w);
2317    }
2318    pub fn uniform_4fv(&self, location: GLint, values: F32VecRef) {
2319        self.get().uniform_4fv(location, values.as_slice());
2320    }
2321    pub fn uniform_matrix_2fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2322        self.get()
2323            .uniform_matrix_2fv(location, transpose, value.as_slice());
2324    }
2325    pub fn uniform_matrix_3fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2326        self.get()
2327            .uniform_matrix_3fv(location, transpose, value.as_slice());
2328    }
2329    pub fn uniform_matrix_4fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2330        self.get()
2331            .uniform_matrix_4fv(location, transpose, value.as_slice());
2332    }
2333    pub fn depth_mask(&self, flag: bool) {
2334        self.get().depth_mask(flag);
2335    }
2336    pub fn depth_range(&self, near: f64, far: f64) {
2337        self.get().depth_range(near, far);
2338    }
2339    #[must_use] pub fn get_active_attrib(&self, program: GLuint, index: GLuint) -> GetActiveAttribReturn {
2340        let r = self.get().get_active_attrib(program, index);
2341        GetActiveAttribReturn {
2342            _0: r.0,
2343            _1: r.1,
2344            _2: r.2.into(),
2345        }
2346    }
2347    #[must_use] pub fn get_active_uniform(&self, program: GLuint, index: GLuint) -> GetActiveUniformReturn {
2348        let r = self.get().get_active_uniform(program, index);
2349        GetActiveUniformReturn {
2350            _0: r.0,
2351            _1: r.1,
2352            _2: r.2.into(),
2353        }
2354    }
2355    #[must_use] pub fn get_active_uniforms_iv(
2356        &self,
2357        program: GLuint,
2358        indices: GLuintVec,
2359        pname: GLenum,
2360    ) -> GLintVec {
2361        self.get()
2362            .get_active_uniforms_iv(program, indices.into_library_owned_vec(), pname)
2363            .into()
2364    }
2365    #[must_use] pub fn get_active_uniform_block_i(
2366        &self,
2367        program: GLuint,
2368        index: GLuint,
2369        pname: GLenum,
2370    ) -> GLint {
2371        self.get().get_active_uniform_block_i(program, index, pname)
2372    }
2373    #[must_use] pub fn get_active_uniform_block_iv(
2374        &self,
2375        program: GLuint,
2376        index: GLuint,
2377        pname: GLenum,
2378    ) -> GLintVec {
2379        self.get()
2380            .get_active_uniform_block_iv(program, index, pname)
2381            .into()
2382    }
2383    #[must_use] pub fn get_active_uniform_block_name(&self, program: GLuint, index: GLuint) -> AzString {
2384        self.get()
2385            .get_active_uniform_block_name(program, index)
2386            .into()
2387    }
2388    #[must_use] pub fn get_attrib_location(&self, program: GLuint, name: &str) -> c_int {
2389        self.get().get_attrib_location(program, name)
2390    }
2391    #[must_use] pub fn get_frag_data_location(&self, program: GLuint, name: &str) -> c_int {
2392        self.get().get_frag_data_location(program, name)
2393    }
2394    #[must_use] pub fn get_uniform_location(&self, program: GLuint, name: &str) -> c_int {
2395        self.get().get_uniform_location(program, name)
2396    }
2397    #[must_use] pub fn get_program_info_log(&self, program: GLuint) -> AzString {
2398        self.get().get_program_info_log(program).into()
2399    }
2400    pub fn get_program_iv(&self, program: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2401        unsafe {
2402            self.get()
2403                .get_program_iv(program, pname, result.as_mut_slice());
2404        }
2405    }
2406    #[must_use] pub fn get_program_binary(&self, program: GLuint) -> GetProgramBinaryReturn {
2407        let r = self.get().get_program_binary(program);
2408        GetProgramBinaryReturn {
2409            _0: r.0.into(),
2410            _1: r.1,
2411        }
2412    }
2413    pub fn program_binary(&self, program: GLuint, format: GLenum, binary: U8VecRef) {
2414        self.get()
2415            .program_binary(program, format, binary.as_slice());
2416    }
2417    pub fn program_parameter_i(&self, program: GLuint, pname: GLenum, value: GLint) {
2418        self.get().program_parameter_i(program, pname, value);
2419    }
2420    pub fn get_vertex_attrib_iv(&self, index: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2421        unsafe {
2422            self.get()
2423                .get_vertex_attrib_iv(index, pname, result.as_mut_slice());
2424        }
2425    }
2426    pub fn get_vertex_attrib_fv(&self, index: GLuint, pname: GLenum, mut result: GLfloatVecRefMut) {
2427        unsafe {
2428            self.get()
2429                .get_vertex_attrib_fv(index, pname, result.as_mut_slice());
2430        }
2431    }
2432    #[must_use] pub fn get_vertex_attrib_pointer_v(&self, index: GLuint, pname: GLenum) -> GLsizeiptr {
2433        self.get().get_vertex_attrib_pointer_v(index, pname)
2434    }
2435    #[must_use] pub fn get_buffer_parameter_iv(&self, target: GLuint, pname: GLenum) -> GLint {
2436        self.get().get_buffer_parameter_iv(target, pname)
2437    }
2438    #[must_use] pub fn get_shader_info_log(&self, shader: GLuint) -> AzString {
2439        self.get().get_shader_info_log(shader).into()
2440    }
2441    #[must_use] pub fn get_string(&self, which: GLenum) -> AzString {
2442        self.get().get_string(which).into()
2443    }
2444    #[must_use] pub fn get_string_i(&self, which: GLenum, index: GLuint) -> AzString {
2445        self.get().get_string_i(which, index).into()
2446    }
2447    pub fn get_shader_iv(&self, shader: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2448        unsafe {
2449            self.get()
2450                .get_shader_iv(shader, pname, result.as_mut_slice());
2451        }
2452    }
2453    #[must_use] pub fn get_shader_precision_format(
2454        &self,
2455        shader_type: GLuint,
2456        precision_type: GLuint,
2457    ) -> GlShaderPrecisionFormatReturn {
2458        let r = self
2459            .get()
2460            .get_shader_precision_format(shader_type, precision_type);
2461        GlShaderPrecisionFormatReturn {
2462            _0: r.0,
2463            _1: r.1,
2464            _2: r.2,
2465        }
2466    }
2467    pub fn compile_shader(&self, shader: GLuint) {
2468        self.get().compile_shader(shader);
2469    }
2470    #[must_use] pub fn create_program(&self) -> GLuint {
2471        self.get().create_program()
2472    }
2473    pub fn delete_program(&self, program: GLuint) {
2474        self.get().delete_program(program);
2475    }
2476    #[must_use] pub fn create_shader(&self, shader_type: GLenum) -> GLuint {
2477        self.get().create_shader(shader_type)
2478    }
2479    pub fn delete_shader(&self, shader: GLuint) {
2480        self.get().delete_shader(shader);
2481    }
2482    pub fn detach_shader(&self, program: GLuint, shader: GLuint) {
2483        self.get().detach_shader(program, shader);
2484    }
2485    pub fn link_program(&self, program: GLuint) {
2486        self.get().link_program(program);
2487    }
2488    pub fn clear_color(&self, r: f32, g: f32, b: f32, a: f32) {
2489        self.get().clear_color(r, g, b, a);
2490    }
2491    pub fn clear(&self, buffer_mask: GLbitfield) {
2492        self.get().clear(buffer_mask);
2493    }
2494    pub fn clear_depth(&self, depth: f64) {
2495        self.get().clear_depth(depth);
2496    }
2497    pub fn clear_stencil(&self, s: GLint) {
2498        self.get().clear_stencil(s);
2499    }
2500    pub fn flush(&self) {
2501        self.get().flush();
2502    }
2503    pub fn finish(&self) {
2504        self.get().finish();
2505    }
2506    #[must_use] pub fn get_error(&self) -> GLenum {
2507        self.get().get_error()
2508    }
2509    pub fn stencil_mask(&self, mask: GLuint) {
2510        self.get().stencil_mask(mask);
2511    }
2512    pub fn stencil_mask_separate(&self, face: GLenum, mask: GLuint) {
2513        self.get().stencil_mask_separate(face, mask);
2514    }
2515    pub fn stencil_func(&self, func: GLenum, ref_: GLint, mask: GLuint) {
2516        self.get().stencil_func(func, ref_, mask);
2517    }
2518    pub fn stencil_func_separate(&self, face: GLenum, func: GLenum, ref_: GLint, mask: GLuint) {
2519        self.get().stencil_func_separate(face, func, ref_, mask);
2520    }
2521    pub fn stencil_op(&self, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
2522        self.get().stencil_op(sfail, dpfail, dppass);
2523    }
2524    pub fn stencil_op_separate(&self, face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
2525        self.get().stencil_op_separate(face, sfail, dpfail, dppass);
2526    }
2527    pub fn egl_image_target_texture2d_oes(&self, target: GLenum, image: GlVoidPtrConst) {
2528        self.get()
2529            .egl_image_target_texture2d_oes(target, image.ptr as *const c_void);
2530    }
2531    pub fn generate_mipmap(&self, target: GLenum) {
2532        self.get().generate_mipmap(target);
2533    }
2534    pub fn insert_event_marker_ext(&self, message: &str) {
2535        self.get().insert_event_marker_ext(message);
2536    }
2537    pub fn push_group_marker_ext(&self, message: &str) {
2538        self.get().push_group_marker_ext(message);
2539    }
2540    pub fn pop_group_marker_ext(&self) {
2541        self.get().pop_group_marker_ext();
2542    }
2543    pub fn debug_message_insert_khr(
2544        &self,
2545        source: GLenum,
2546        type_: GLenum,
2547        id: GLuint,
2548        severity: GLenum,
2549        message: &str,
2550    ) {
2551        self.get()
2552            .debug_message_insert_khr(source, type_, id, severity, message);
2553    }
2554    pub fn push_debug_group_khr(&self, source: GLenum, id: GLuint, message: &str) {
2555        self.get()
2556            .push_debug_group_khr(source, id, message);
2557    }
2558    pub fn pop_debug_group_khr(&self) {
2559        self.get().pop_debug_group_khr();
2560    }
2561    #[must_use] pub fn fence_sync(&self, condition: GLenum, flags: GLbitfield) -> GLsyncPtr {
2562        GLsyncPtr::new(self.get().fence_sync(condition, flags))
2563    }
2564    #[must_use] pub fn client_wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) -> u32 {
2565        self.get().client_wait_sync(sync.get(), flags, timeout)
2566    }
2567    pub fn wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) {
2568        self.get().wait_sync(sync.get(), flags, timeout);
2569    }
2570    pub fn delete_sync(&self, sync: GLsyncPtr) {
2571        self.get().delete_sync(sync.get());
2572    }
2573    pub fn texture_range_apple(&self, target: GLenum, data: U8VecRef) {
2574        self.get().texture_range_apple(target, data.as_slice());
2575    }
2576    #[must_use] pub fn gen_fences_apple(&self, n: GLsizei) -> GLuintVec {
2577        self.get().gen_fences_apple(n).into()
2578    }
2579    pub fn delete_fences_apple(&self, fences: GLuintVecRef) {
2580        self.get().delete_fences_apple(fences.as_slice());
2581    }
2582    pub fn set_fence_apple(&self, fence: GLuint) {
2583        self.get().set_fence_apple(fence);
2584    }
2585    pub fn finish_fence_apple(&self, fence: GLuint) {
2586        self.get().finish_fence_apple(fence);
2587    }
2588    pub fn test_fence_apple(&self, fence: GLuint) {
2589        self.get().test_fence_apple(fence);
2590    }
2591    #[must_use] pub fn test_object_apple(&self, object: GLenum, name: GLuint) -> GLboolean {
2592        self.get().test_object_apple(object, name)
2593    }
2594    pub fn finish_object_apple(&self, object: GLenum, name: GLuint) {
2595        self.get().finish_object_apple(object, name);
2596    }
2597    #[must_use] pub fn get_frag_data_index(&self, program: GLuint, name: &str) -> GLint {
2598        self.get().get_frag_data_index(program, name)
2599    }
2600    pub fn blend_barrier_khr(&self) {
2601        self.get().blend_barrier_khr();
2602    }
2603    pub fn bind_frag_data_location_indexed(
2604        &self,
2605        program: GLuint,
2606        color_number: GLuint,
2607        index: GLuint,
2608        name: &str,
2609    ) {
2610        self.get()
2611            .bind_frag_data_location_indexed(program, color_number, index, name);
2612    }
2613    #[must_use] pub fn get_debug_messages(&self) -> DebugMessageVec {
2614        let dmv: Vec<DebugMessage> = self
2615            .get()
2616            .get_debug_messages()
2617            .into_iter()
2618            .map(|d| DebugMessage {
2619                message: d.message.into(),
2620                source: d.source,
2621                ty: d.ty,
2622                id: d.id,
2623                severity: d.severity,
2624            })
2625            .collect();
2626        dmv.into()
2627    }
2628    pub fn provoking_vertex_angle(&self, mode: GLenum) {
2629        self.get().provoking_vertex_angle(mode);
2630    }
2631    #[must_use] pub fn gen_vertex_arrays_apple(&self, n: GLsizei) -> GLuintVec {
2632        self.get().gen_vertex_arrays_apple(n).into()
2633    }
2634    pub fn bind_vertex_array_apple(&self, vao: GLuint) {
2635        self.get().bind_vertex_array_apple(vao);
2636    }
2637    pub fn delete_vertex_arrays_apple(&self, vertex_arrays: GLuintVecRef) {
2638        self.get()
2639            .delete_vertex_arrays_apple(vertex_arrays.as_slice());
2640    }
2641    pub fn copy_texture_chromium(
2642        &self,
2643        source_id: GLuint,
2644        source_level: GLint,
2645        dest_target: GLenum,
2646        dest_id: GLuint,
2647        dest_level: GLint,
2648        internal_format: GLint,
2649        dest_type: GLenum,
2650        unpack_flip_y: GLboolean,
2651        unpack_premultiply_alpha: GLboolean,
2652        unpack_unmultiply_alpha: GLboolean,
2653    ) {
2654        self.get().copy_texture_chromium(
2655            source_id,
2656            source_level,
2657            dest_target,
2658            dest_id,
2659            dest_level,
2660            internal_format,
2661            dest_type,
2662            unpack_flip_y,
2663            unpack_premultiply_alpha,
2664            unpack_unmultiply_alpha,
2665        );
2666    }
2667    pub fn copy_sub_texture_chromium(
2668        &self,
2669        source_id: GLuint,
2670        source_level: GLint,
2671        dest_target: GLenum,
2672        dest_id: GLuint,
2673        dest_level: GLint,
2674        x_offset: GLint,
2675        y_offset: GLint,
2676        x: GLint,
2677        y: GLint,
2678        width: GLsizei,
2679        height: GLsizei,
2680        unpack_flip_y: GLboolean,
2681        unpack_premultiply_alpha: GLboolean,
2682        unpack_unmultiply_alpha: GLboolean,
2683    ) {
2684        self.get().copy_sub_texture_chromium(
2685            source_id,
2686            source_level,
2687            dest_target,
2688            dest_id,
2689            dest_level,
2690            x_offset,
2691            y_offset,
2692            x,
2693            y,
2694            width,
2695            height,
2696            unpack_flip_y,
2697            unpack_premultiply_alpha,
2698            unpack_unmultiply_alpha,
2699        );
2700    }
2701    pub fn egl_image_target_renderbuffer_storage_oes(&self, target: u32, image: GlVoidPtrConst) {
2702        self.get().egl_image_target_renderbuffer_storage_oes(
2703            target,
2704            image.ptr as *const c_void,
2705        );
2706    }
2707    pub fn copy_texture_3d_angle(
2708        &self,
2709        source_id: GLuint,
2710        source_level: GLint,
2711        dest_target: GLenum,
2712        dest_id: GLuint,
2713        dest_level: GLint,
2714        internal_format: GLint,
2715        dest_type: GLenum,
2716        unpack_flip_y: GLboolean,
2717        unpack_premultiply_alpha: GLboolean,
2718        unpack_unmultiply_alpha: GLboolean,
2719    ) {
2720        self.get().copy_texture_3d_angle(
2721            source_id,
2722            source_level,
2723            dest_target,
2724            dest_id,
2725            dest_level,
2726            internal_format,
2727            dest_type,
2728            unpack_flip_y,
2729            unpack_premultiply_alpha,
2730            unpack_unmultiply_alpha,
2731        );
2732    }
2733    pub fn copy_sub_texture_3d_angle(
2734        &self,
2735        source_id: GLuint,
2736        source_level: GLint,
2737        dest_target: GLenum,
2738        dest_id: GLuint,
2739        dest_level: GLint,
2740        x_offset: GLint,
2741        y_offset: GLint,
2742        z_offset: GLint,
2743        x: GLint,
2744        y: GLint,
2745        z: GLint,
2746        width: GLsizei,
2747        height: GLsizei,
2748        depth: GLsizei,
2749        unpack_flip_y: GLboolean,
2750        unpack_premultiply_alpha: GLboolean,
2751        unpack_unmultiply_alpha: GLboolean,
2752    ) {
2753        self.get().copy_sub_texture_3d_angle(
2754            source_id,
2755            source_level,
2756            dest_target,
2757            dest_id,
2758            dest_level,
2759            x_offset,
2760            y_offset,
2761            z_offset,
2762            x,
2763            y,
2764            z,
2765            width,
2766            height,
2767            depth,
2768            unpack_flip_y,
2769            unpack_premultiply_alpha,
2770            unpack_unmultiply_alpha,
2771        );
2772    }
2773    pub fn buffer_storage(
2774        &self,
2775        target: GLenum,
2776        size: GLsizeiptr,
2777        data: GlVoidPtrConst,
2778        flags: GLbitfield,
2779    ) {
2780        self.get().buffer_storage(target, size, data.ptr, flags);
2781    }
2782    pub fn flush_mapped_buffer_range(&self, target: GLenum, offset: GLintptr, length: GLsizeiptr) {
2783        self.get().flush_mapped_buffer_range(target, offset, length);
2784    }
2785}
2786
2787impl PartialEq for GlContextPtr {
2788    fn eq(&self, rhs: &Self) -> bool {
2789        self.as_usize().eq(&rhs.as_usize())
2790    }
2791}
2792
2793impl Eq for GlContextPtr {}
2794
2795impl PartialOrd for GlContextPtr {
2796    fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
2797        self.as_usize().partial_cmp(&rhs.as_usize())
2798    }
2799}
2800
2801impl Ord for GlContextPtr {
2802    fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
2803        self.as_usize().cmp(&rhs.as_usize())
2804    }
2805}
2806
2807/// Saved OpenGL state for save/restore around framebuffer operations.
2808/// Used by `Texture::clear()` and `GlShader::draw()` to avoid corrupting
2809/// the caller's GL state.
2810// the `current_` prefix is intentional: each field holds the saved CURRENT GL
2811// binding captured at save() to be restored in restore().
2812#[allow(clippy::struct_field_names)]
2813struct GlStateSave {
2814    current_multisample: [u8; 1],
2815    current_index_buffer: [i32; 1],
2816    current_vertex_buffer: [i32; 1],
2817    current_vertex_array_object: [i32; 1],
2818    current_program: [i32; 1],
2819    current_framebuffers: [i32; 1],
2820    current_renderbuffers: [i32; 1],
2821    current_texture_2d: [i32; 1],
2822}
2823
2824impl GlStateSave {
2825    fn save(gl_context: &GlContextPtr) -> Self {
2826        let mut s = Self {
2827            current_multisample: [0],
2828            current_index_buffer: [0],
2829            current_vertex_buffer: [0],
2830            current_vertex_array_object: [0],
2831            current_program: [0],
2832            current_framebuffers: [0],
2833            current_renderbuffers: [0],
2834            current_texture_2d: [0],
2835        };
2836
2837        gl_context.get_boolean_v(gl::MULTISAMPLE, (&mut s.current_multisample[..]).into());
2838        gl_context.get_integer_v(gl::ARRAY_BUFFER_BINDING, (&mut s.current_vertex_buffer[..]).into());
2839        gl_context.get_integer_v(gl::ELEMENT_ARRAY_BUFFER_BINDING, (&mut s.current_index_buffer[..]).into());
2840        gl_context.get_integer_v(gl::CURRENT_PROGRAM, (&mut s.current_program[..]).into());
2841        gl_context.get_integer_v(gl::VERTEX_ARRAY_BINDING, (&mut s.current_vertex_array_object[..]).into());
2842        gl_context.get_integer_v(gl::RENDERBUFFER, (&mut s.current_renderbuffers[..]).into());
2843        gl_context.get_integer_v(gl::FRAMEBUFFER, (&mut s.current_framebuffers[..]).into());
2844        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut s.current_texture_2d[..]).into());
2845
2846        s
2847    }
2848
2849    // OpenGL binding: state values passed to the gl API as GLuint/GLsizei.
2850    #[allow(clippy::cast_sign_loss)]
2851    fn restore(&self, gl_context: &GlContextPtr) {
2852        if u32::from(self.current_multisample[0]) == gl::TRUE {
2853            gl_context.enable(gl::MULTISAMPLE);
2854        }
2855        gl_context.bind_framebuffer(gl::FRAMEBUFFER, self.current_framebuffers[0] as u32);
2856        gl_context.bind_texture(gl::TEXTURE_2D, self.current_texture_2d[0] as u32);
2857        gl_context.bind_buffer(gl::RENDERBUFFER, self.current_renderbuffers[0] as u32);
2858        gl_context.bind_vertex_array(self.current_vertex_array_object[0] as u32);
2859        gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.current_index_buffer[0] as u32);
2860        gl_context.bind_buffer(gl::ARRAY_BUFFER, self.current_vertex_buffer[0] as u32);
2861        gl_context.use_program(self.current_program[0] as u32);
2862    }
2863}
2864
2865/// AUDIT: RAII guard that deletes a transient framebuffer + renderbuffer on
2866/// scope exit. Used by `Texture::clear` and `GlShader::draw` so that a panic
2867/// mid-path (e.g. an `.unwrap()` on an empty gen-list, or any GL step that
2868/// panics) can't leak the FBO/RBO. Ids of `0` are skipped (GL treats delete-0
2869/// as a no-op anyway, but this keeps intent explicit).
2870struct FboRboGuard<'a> {
2871    gl_context: &'a GlContextPtr,
2872    framebuffer_id: GLuint,
2873    renderbuffer_id: GLuint,
2874}
2875
2876impl Drop for FboRboGuard<'_> {
2877    fn drop(&mut self) {
2878        if self.framebuffer_id != 0 {
2879            self.gl_context
2880                .delete_framebuffers((&[self.framebuffer_id])[..].into());
2881        }
2882        if self.renderbuffer_id != 0 {
2883            self.gl_context
2884                .delete_renderbuffers((&[self.renderbuffer_id])[..].into());
2885        }
2886    }
2887}
2888
2889/// OpenGL texture, use `ReadOnlyWindow::create_texture` to create a texture
2890#[repr(C)]
2891pub struct Texture {
2892    /// A reference-counted pointer to the OpenGL context (so that the texture can be deleted in
2893    /// the destructor)
2894    pub gl_context: GlContextPtr,
2895    /// Raw OpenGL texture ID
2896    pub texture_id: GLuint,
2897    /// Reference count, shared across
2898    pub refcount: *const AtomicUsize,
2899    /// Size of this texture (in pixels)
2900    pub size: PhysicalSizeU32,
2901    /// Format of the texture (rgba8, brga8, etc.)
2902    pub format: RawImageFormat,
2903    /// Background color of this texture
2904    pub background_color: ColorU,
2905    /// Hints and flags for optimization purposes
2906    pub flags: TextureFlags,
2907    pub run_destructor: bool,
2908}
2909
2910impl Clone for Texture {
2911    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
2912    fn clone(&self) -> Self {
2913        unsafe {
2914            (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst);
2915        }
2916        Self {
2917            gl_context: self.gl_context.clone(),
2918            texture_id: self.texture_id,
2919            refcount: self.refcount,
2920            size: self.size,
2921            format: self.format,
2922            background_color: self.background_color,
2923            flags: self.flags,
2924            run_destructor: true,
2925        }
2926    }
2927}
2928
2929impl_option!(
2930    Texture,
2931    OptionTexture,
2932    copy = false,
2933    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2934);
2935
2936impl Texture {
2937    #[must_use] pub fn create(
2938        texture_id: GLuint,
2939        flags: TextureFlags,
2940        size: PhysicalSizeU32,
2941        background_color: ColorU,
2942        gl_context: GlContextPtr,
2943        format: RawImageFormat,
2944    ) -> Self {
2945        Self {
2946            texture_id,
2947            flags,
2948            size,
2949            background_color,
2950            gl_context,
2951            format,
2952            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
2953            run_destructor: true,
2954        }
2955    }
2956
2957    // OpenGL binding: gl::* enum constants and texture dimensions are passed as
2958    // GLint/GLsizei (i32); the values are GL-bounded and the `as i32` casts are the
2959    // idiomatic form for the gl API.
2960    #[allow(clippy::cast_possible_wrap)]
2961    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
2962    #[must_use] pub fn allocate_rgba8(
2963        gl_context: GlContextPtr,
2964        size: PhysicalSizeU32,
2965        background: ColorU,
2966    ) -> Self {
2967        let textures = gl_context.gen_textures(1);
2968        let texture_id = textures.as_ref()[0];
2969
2970        let mut current_texture_2d = [0_i32];
2971        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut current_texture_2d[..]).into());
2972
2973        gl_context.bind_texture(gl::TEXTURE_2D, texture_id);
2974        gl_context.tex_image_2d(
2975            gl::TEXTURE_2D,
2976            0,
2977            gl::RGBA as i32,
2978            size.width as i32,
2979            size.height as i32,
2980            0,
2981            gl::RGBA,
2982            gl::UNSIGNED_BYTE,
2983            None.into(),
2984        );
2985        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
2986        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
2987        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
2988        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
2989        gl_context.bind_texture(gl::TEXTURE_2D, current_texture_2d[0] as u32);
2990
2991        Self::create(
2992            texture_id,
2993            TextureFlags {
2994                is_opaque: false,
2995                is_video_texture: false,
2996            },
2997            size,
2998            background,
2999            gl_context,
3000            // Format is BGRA8 for WebRender integration, despite the GL upload using RGBA
3001            RawImageFormat::BGRA8,
3002        )
3003    }
3004
3005    /// # Panics
3006    ///
3007    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
3008    // OpenGL binding: gl::* enum constants and texture dimensions passed as
3009    // GLint/GLsizei (i32); values are GL-bounded, `as i32` is the idiomatic form.
3010    #[allow(clippy::cast_possible_wrap)]
3011    pub fn clear(&mut self) {
3012        let saved = GlStateSave::save(&self.gl_context);
3013
3014        let framebuffers = self.gl_context.gen_framebuffers(1);
3015        let framebuffer_id = *framebuffers.get(0).unwrap();
3016        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
3017        // panic in `gen_renderbuffers().get(0).unwrap()` can't leak it.
3018        let mut fbo_rbo_guard = FboRboGuard {
3019            gl_context: &self.gl_context,
3020            framebuffer_id,
3021            renderbuffer_id: 0,
3022        };
3023        self.gl_context
3024            .bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
3025
3026        let depthbuffers = self.gl_context.gen_renderbuffers(1);
3027        let depthbuffer_id = *depthbuffers.get(0).unwrap();
3028        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
3029
3030        self.gl_context
3031            .bind_texture(gl::TEXTURE_2D, self.texture_id);
3032        self.gl_context.tex_image_2d(
3033            gl::TEXTURE_2D,
3034            0,
3035            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
3036            self.size.width as i32,
3037            self.size.height as i32,
3038            0,
3039            gl::RGBA, // gl::BGRA?
3040            gl::UNSIGNED_BYTE,
3041            None.into(),
3042        );
3043        self.gl_context
3044            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
3045        self.gl_context
3046            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
3047        self.gl_context.tex_parameter_i(
3048            gl::TEXTURE_2D,
3049            gl::TEXTURE_WRAP_S,
3050            gl::CLAMP_TO_EDGE as i32,
3051        );
3052        self.gl_context.tex_parameter_i(
3053            gl::TEXTURE_2D,
3054            gl::TEXTURE_WRAP_T,
3055            gl::CLAMP_TO_EDGE as i32,
3056        );
3057
3058        self.gl_context
3059            .bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
3060        self.gl_context.renderbuffer_storage(
3061            gl::RENDERBUFFER,
3062            gl::DEPTH_COMPONENT,
3063            self.size.width as i32,
3064            self.size.height as i32,
3065        );
3066        self.gl_context.framebuffer_renderbuffer(
3067            gl::FRAMEBUFFER,
3068            gl::DEPTH_ATTACHMENT,
3069            gl::RENDERBUFFER,
3070            depthbuffer_id,
3071        );
3072
3073        self.gl_context.framebuffer_texture_2d(
3074            gl::FRAMEBUFFER,
3075            gl::COLOR_ATTACHMENT0,
3076            gl::TEXTURE_2D,
3077            self.texture_id,
3078            0,
3079        );
3080        self.gl_context
3081            .draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
3082
3083        let clear_color: ColorF = self.background_color.into();
3084        self.gl_context
3085            .clear_color(clear_color.r, clear_color.g, clear_color.b, clear_color.a);
3086        self.gl_context.clear_depth(0.0);
3087        self.gl_context
3088            .clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
3089
3090        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
3091        // also runs on an unwinding panic), so we restore state and let the
3092        // guard reclaim the GL objects at scope exit.
3093        saved.restore(&self.gl_context);
3094        drop(fbo_rbo_guard);
3095    }
3096
3097    #[must_use] pub fn get_descriptor(&self) -> ImageDescriptor {
3098        ImageDescriptor {
3099            format: self.format,
3100            width: self.size.width as usize,
3101            height: self.size.height as usize,
3102            stride: None.into(),
3103            offset: 0,
3104            flags: ImageDescriptorFlags {
3105                is_opaque: self.flags.is_opaque,
3106                // The texture gets mapped 1:1 onto the display, so there is no need for mipmaps
3107                allow_mipmaps: false,
3108            },
3109        }
3110    }
3111
3112    /// Draws a `TessellatedGPUSvgNode` with the given color to the texture
3113    pub fn draw_tesselated_svg_gpu_node(
3114        &mut self,
3115        node: &TessellatedGPUSvgNode,
3116        size: PhysicalSizeU32,
3117        color: ColorU,
3118        transforms: StyleTransformVec,
3119    ) -> bool {
3120        node.draw(self, size, color, transforms)
3121    }
3122
3123    /// Draws a `TessellatedColoredGPUSvgNode` to the texture
3124    pub fn draw_tesselated_colored_svg_gpu_node(
3125        &mut self,
3126        node: &crate::svg::TessellatedColoredGPUSvgNode,
3127        size: PhysicalSizeU32,
3128        transforms: StyleTransformVec,
3129    ) -> bool {
3130        node.draw(self, size, transforms)
3131    }
3132}
3133
3134#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3135#[repr(C)]
3136pub struct TextureFlags {
3137    /// Whether this texture contains an alpha component
3138    pub is_opaque: bool,
3139    /// Optimization: use the compositor instead of OpenGL for energy optimization
3140    pub is_video_texture: bool,
3141}
3142
3143impl ::core::fmt::Display for Texture {
3144    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3145        write!(
3146            f,
3147            "Texture {{ id: {}, {}x{} }}",
3148            self.texture_id, self.size.width, self.size.height
3149        )
3150    }
3151}
3152
3153macro_rules! impl_traits_for_gl_object {
3154    ($struct_name:ident, $gl_id_field:ident) => {
3155        impl ::core::fmt::Debug for $struct_name {
3156            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3157                write!(f, "{}", self)
3158            }
3159        }
3160
3161        impl Hash for $struct_name {
3162            fn hash<H: Hasher>(&self, state: &mut H) {
3163                self.$gl_id_field.hash(state);
3164            }
3165        }
3166
3167        impl PartialEq for $struct_name {
3168            fn eq(&self, other: &$struct_name) -> bool {
3169                self.$gl_id_field == other.$gl_id_field
3170            }
3171        }
3172
3173        impl Eq for $struct_name {}
3174
3175        impl PartialOrd for $struct_name {
3176            fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
3177                Some((self.$gl_id_field).cmp(&(other.$gl_id_field)))
3178            }
3179        }
3180
3181        impl Ord for $struct_name {
3182            fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
3183                (self.$gl_id_field).cmp(&(other.$gl_id_field))
3184            }
3185        }
3186    };
3187}
3188
3189impl Texture {
3190    /// GPU painting: stamp one soft-brush dab centered at (`cx`, `cy`) in texture
3191    /// pixel coordinates (origin top-left, matching [`RawImage::paint_dot`]).
3192    /// No-op if the GL context is unusable -- the caller should then use the CPU
3193    /// `RawImage` path (`GlContextPtr::is_gl_usable`).
3194    pub fn paint_dot(&mut self, cx: f32, cy: f32, brush: Brush) {
3195        self.paint_stroke(cx, cy, cx, cy, brush);
3196    }
3197
3198    /// GPU painting: stamp dabs along (`x0`,`y0`)->(`x1`,`y1`) into this texture
3199    /// via an FBO + the soft-brush shader, alpha-over blended. Same spacing +
3200    /// falloff as the CPU `RawImage::paint_stroke`. No-op if GL is unusable.
3201    #[allow(clippy::suboptimal_flops)] // mul_add not guaranteed faster/available without target +fma; keep explicit a*b+c
3202    #[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
3203    #[allow(clippy::many_single_char_names)] // domain-standard colour/coordinate component names
3204    pub fn paint_stroke(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, brush: Brush) {
3205        let gl = self.gl_context.clone();
3206        let prog = gl.get_brush_shader();
3207        let (tw, th) = (self.size.width as f32, self.size.height as f32);
3208        // `!(radius > 0.0)` intentionally also rejects NaN (`radius <= 0.0` would not).
3209        #[allow(clippy::neg_cmp_op_on_partial_ord)]
3210        if prog == 0 || self.texture_id == 0 || !(brush.radius > 0.0) || tw <= 0.0 || th <= 0.0 {
3211            return;
3212        }
3213
3214        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
3215        let vbo = gl.gen_buffers(1).get(0).copied().unwrap_or(0);
3216        if fbo == 0 || vbo == 0 {
3217            if fbo != 0 {
3218                gl.delete_framebuffers((&[fbo][..]).into());
3219            }
3220            if vbo != 0 {
3221                gl.delete_buffers((&[vbo][..]).into());
3222            }
3223            return;
3224        }
3225
3226        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
3227        gl.framebuffer_texture_2d(
3228            gl::FRAMEBUFFER,
3229            gl::COLOR_ATTACHMENT0,
3230            gl::TEXTURE_2D,
3231            self.texture_id,
3232            0,
3233        );
3234        gl.viewport(0, 0, self.size.width as i32, self.size.height as i32);
3235        gl.enable(gl::BLEND);
3236        gl.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
3237        gl.use_program(prog);
3238
3239        let a = (f32::from(brush.color.a) / 255.0) * brush.flow.clamp(0.0, 1.0);
3240        gl.uniform_4f(
3241            gl.get_uniform_location(prog, "uColor"),
3242            f32::from(brush.color.r) / 255.0,
3243            f32::from(brush.color.g) / 255.0,
3244            f32::from(brush.color.b) / 255.0,
3245            a,
3246        );
3247        gl.uniform_1f(gl.get_uniform_location(prog, "uHardness"), brush.hardness);
3248
3249        gl.bind_buffer(gl::ARRAY_BUFFER, vbo);
3250        gl.enable_vertex_attrib_array(0);
3251        gl.enable_vertex_attrib_array(1);
3252        gl.vertex_attrib_pointer_f32(0, 2, false, 16, 0);
3253        gl.vertex_attrib_pointer_f32(1, 2, false, 16, 8);
3254
3255        let dx = x1 - x0;
3256        let dy = y1 - y0;
3257        let len = dx.hypot(dy);
3258        let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
3259        let n = ((len / step).floor() as i32).max(0);
3260        let r = brush.radius;
3261        for i in 0..=n {
3262            let t = if n == 0 { 1.0 } else { i as f32 / n as f32 };
3263            let px = x0 + dx * t;
3264            let py = y0 + dy * t;
3265            // dab bbox -> NDC; y is flipped so (0,0) is top-left like the CPU path.
3266            let nx = |x: f32| (x / tw) * 2.0 - 1.0;
3267            let ny = |y: f32| 1.0 - (y / th) * 2.0;
3268            let (l, rr, tp, bt) = (nx(px - r), nx(px + r), ny(py - r), ny(py + r));
3269            // TRIANGLE_STRIP: TL, BL, TR, BR -- interleaved (pos.x, pos.y, uv.x, uv.y).
3270            let verts: [f32; 16] = [
3271                l, tp, -1.0, -1.0, l, bt, -1.0, 1.0, rr, tp, 1.0, -1.0, rr, bt, 1.0, 1.0,
3272            ];
3273            gl.buffer_data_untyped(
3274                gl::ARRAY_BUFFER,
3275                (verts.len() * size_of::<f32>()) as isize,
3276                GlVoidPtrConst {
3277                    ptr: verts.as_ptr() as *const GLvoid,
3278                    run_destructor: false,
3279                },
3280                gl::STREAM_DRAW,
3281            );
3282            gl.draw_arrays(gl::TRIANGLE_STRIP, 0, 4);
3283        }
3284
3285        gl.disable_vertex_attrib_array(0);
3286        gl.disable_vertex_attrib_array(1);
3287        gl.bind_buffer(gl::ARRAY_BUFFER, 0);
3288        gl.disable(gl::BLEND);
3289        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
3290        gl.delete_buffers((&[vbo][..]).into());
3291        gl.delete_framebuffers((&[fbo][..]).into());
3292    }
3293
3294    /// Read this texture's pixels back into an RGBA8 `RawImage` (top-left origin)
3295    /// -- for exporting the painted canvas to disk. Binds an FBO + glReadPixels.
3296    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
3297    #[must_use] pub fn copy_to_raw_image(&self) -> RawImage {
3298        let gl = self.gl_context.clone();
3299        let (w, h) = (self.size.width as i32, self.size.height as i32);
3300        if self.texture_id == 0 || w <= 0 || h <= 0 {
3301            return RawImage::null_image();
3302        }
3303        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
3304        if fbo == 0 {
3305            return RawImage::null_image();
3306        }
3307        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
3308        gl.framebuffer_texture_2d(
3309            gl::FRAMEBUFFER,
3310            gl::COLOR_ATTACHMENT0,
3311            gl::TEXTURE_2D,
3312            self.texture_id,
3313            0,
3314        );
3315        let pixels = gl.read_pixels(0, 0, w, h, gl::RGBA, gl::UNSIGNED_BYTE);
3316        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
3317        gl.delete_framebuffers((&[fbo][..]).into());
3318
3319        // glReadPixels uses a bottom-left origin; flip rows to top-left for saving.
3320        let mut bytes = pixels.into_library_owned_vec();
3321        let row = (w as usize) * 4;
3322        let hh = h as usize;
3323        if row > 0 && bytes.len() >= row * hh {
3324            for y in 0..hh / 2 {
3325                let yi = y * row;
3326                let yo = (hh - 1 - y) * row;
3327                for k in 0..row {
3328                    bytes.swap(yi + k, yo + k);
3329                }
3330            }
3331        }
3332        RawImage {
3333            pixels: RawImageData::U8(bytes.into()),
3334            width: w as usize,
3335            height: h as usize,
3336            premultiplied_alpha: true,
3337            data_format: RawImageFormat::RGBA8,
3338            tag: Vec::new().into(),
3339        }
3340    }
3341}
3342
3343impl_traits_for_gl_object!(Texture, texture_id);
3344
3345impl Drop for Texture {
3346    fn drop(&mut self) {
3347        // AUDIT: mirror `GlContextPtr::drop`. Without this guard a C-ABI
3348        // double-drop (drop_in_place run twice on the same byte-copied struct)
3349        // does a second `fetch_sub` on the already-freed refcount box (UAF) and
3350        // a second `delete_textures`. The first drop clears `run_destructor`, so
3351        // the second is a no-op.
3352        if !self.run_destructor {
3353            return;
3354        }
3355        self.run_destructor = false;
3356        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3357        if copies == 1 {
3358            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3359            self.gl_context
3360                .delete_textures((&[self.texture_id])[..].into());
3361        }
3362    }
3363}
3364
3365/// Describes the vertex layout and offsets
3366#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3367#[repr(C)]
3368pub struct VertexLayout {
3369    pub fields: VertexAttributeVec,
3370}
3371
3372impl_vec!(VertexAttribute, VertexAttributeVec, VertexAttributeVecDestructor, VertexAttributeVecDestructorType, VertexAttributeVecSlice, OptionVertexAttribute);
3373impl_vec_debug!(VertexAttribute, VertexAttributeVec);
3374impl_vec_partialord!(VertexAttribute, VertexAttributeVec);
3375impl_vec_ord!(VertexAttribute, VertexAttributeVec);
3376impl_vec_clone!(
3377    VertexAttribute,
3378    VertexAttributeVec,
3379    VertexAttributeVecDestructor
3380);
3381impl_vec_partialeq!(VertexAttribute, VertexAttributeVec);
3382impl_vec_eq!(VertexAttribute, VertexAttributeVec);
3383impl_vec_hash!(VertexAttribute, VertexAttributeVec);
3384
3385impl VertexLayout {
3386    /// Submits the vertex buffer description to OpenGL
3387    // OpenGL binding: vertex-attribute layout (locations, item counts, strides,
3388    // offsets) passed to the gl API as GLuint/GLint/GLsizei; values are GL-bounded.
3389    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
3390    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3391    pub fn bind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
3392        const VERTICES_ARE_NORMALIZED: bool = false;
3393
3394        let mut offset = 0;
3395
3396        let stride_between_vertices: usize =
3397            self.fields.iter().map(VertexAttribute::get_stride).sum();
3398
3399        for vertex_attribute in &self.fields {
3400            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
3401                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
3402                |ll| *ll as i32,
3403            );
3404
3405            gl_context.vertex_attrib_pointer(
3406                attribute_location as u32,
3407                vertex_attribute.item_count as i32,
3408                vertex_attribute.attribute_type.get_gl_id(),
3409                VERTICES_ARE_NORMALIZED,
3410                stride_between_vertices as i32,
3411                offset as u32,
3412            );
3413            gl_context.enable_vertex_attrib_array(attribute_location as u32);
3414            offset += vertex_attribute.get_stride();
3415        }
3416    }
3417
3418    /// Unsets the vertex buffer description
3419    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3420    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts
3421    pub fn unbind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
3422        for vertex_attribute in &self.fields {
3423            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
3424                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
3425                |ll| *ll as i32,
3426            );
3427            gl_context.disable_vertex_attrib_array(attribute_location as u32);
3428        }
3429    }
3430}
3431
3432#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3433#[repr(C)]
3434pub struct VertexAttribute {
3435    /// Attribute name of the vertex attribute in the vertex shader, i.e. `"vAttrXY"`
3436    pub va_name: AzString,
3437    /// If the vertex shader has a specific location, (like `layout(location = 2) vAttrXY`),
3438    /// use this instead of the name to look up the uniform location.
3439    pub layout_location: OptionUsize,
3440    /// Type of items of this attribute (i.e. for a `FloatVec2`, would be
3441    /// `VertexAttributeType::Float`)
3442    pub attribute_type: VertexAttributeType,
3443    /// Number of items of this attribute (i.e. for a `FloatVec2`, would be `2` (= 2 consecutive
3444    /// f32 values))
3445    pub item_count: usize,
3446}
3447
3448impl_option!(
3449    VertexAttribute,
3450    OptionVertexAttribute,
3451    copy = false,
3452    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3453);
3454
3455impl VertexAttribute {
3456    #[must_use] pub const fn get_stride(&self) -> usize {
3457        self.attribute_type.get_mem_size() * self.item_count
3458    }
3459}
3460
3461#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3462#[repr(C)]
3463pub enum VertexAttributeType {
3464    /// Vertex attribute has type `f32`
3465    Float,
3466    /// Vertex attribute has type `f64`
3467    Double,
3468    /// Vertex attribute has type `u8`
3469    UnsignedByte,
3470    /// Vertex attribute has type `u16`
3471    UnsignedShort,
3472    /// Vertex attribute has type `u32`
3473    UnsignedInt,
3474}
3475
3476impl VertexAttributeType {
3477    /// Returns the OpenGL id for the vertex attribute type, ex. `gl::UNSIGNED_BYTE` for
3478    /// `VertexAttributeType::UnsignedByte`.
3479    #[must_use] pub const fn get_gl_id(&self) -> GLuint {
3480        use self::VertexAttributeType::{Float, Double, UnsignedByte, UnsignedShort, UnsignedInt};
3481        match self {
3482            Float => gl::FLOAT,
3483            Double => gl::DOUBLE,
3484            UnsignedByte => gl::UNSIGNED_BYTE,
3485            UnsignedShort => gl::UNSIGNED_SHORT,
3486            UnsignedInt => gl::UNSIGNED_INT,
3487        }
3488    }
3489
3490    #[must_use] pub const fn get_mem_size(&self) -> usize {
3491        use core::mem;
3492
3493        use self::VertexAttributeType::{Float, Double, UnsignedByte, UnsignedShort, UnsignedInt};
3494        match self {
3495            Float => size_of::<f32>(),
3496            Double => size_of::<f64>(),
3497            UnsignedByte => size_of::<u8>(),
3498            UnsignedShort => size_of::<u16>(),
3499            UnsignedInt => size_of::<u32>(),
3500        }
3501    }
3502}
3503
3504pub trait VertexLayoutDescription {
3505    fn get_description() -> VertexLayout;
3506}
3507
3508#[derive(Debug, PartialEq, Eq, PartialOrd)]
3509#[repr(C)]
3510pub struct VertexArrayObject {
3511    pub vertex_layout: VertexLayout,
3512    pub vao_id: GLuint,
3513    pub gl_context: GlContextPtr,
3514    pub refcount: *const AtomicUsize,
3515    pub run_destructor: bool,
3516}
3517
3518impl VertexArrayObject {
3519    #[must_use] pub fn new(vertex_layout: VertexLayout, vao_id: GLuint, gl_context: GlContextPtr) -> Self {
3520        Self {
3521            vertex_layout,
3522            vao_id,
3523            gl_context,
3524            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3525            run_destructor: true,
3526        }
3527    }
3528}
3529
3530impl Clone for VertexArrayObject {
3531    fn clone(&self) -> Self {
3532        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
3533        Self {
3534            vertex_layout: self.vertex_layout.clone(),
3535            vao_id: self.vao_id,
3536            gl_context: self.gl_context.clone(),
3537            refcount: self.refcount,
3538            run_destructor: true,
3539        }
3540    }
3541}
3542
3543impl Drop for VertexArrayObject {
3544    fn drop(&mut self) {
3545        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
3546        // freeing the refcount box twice (use-after-free) + double delete.
3547        if !self.run_destructor {
3548            return;
3549        }
3550        self.run_destructor = false;
3551        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3552        if copies == 1 {
3553            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3554            self.gl_context
3555                .delete_vertex_arrays((&[self.vao_id])[..].into());
3556        }
3557    }
3558}
3559
3560#[repr(C)]
3561pub struct VertexBuffer {
3562    pub vao: VertexArrayObject,
3563    pub vertex_buffer_id: GLuint,
3564    pub vertex_buffer_len: usize,
3565    pub index_buffer_id: GLuint,
3566    pub index_buffer_len: usize,
3567    pub refcount: *const AtomicUsize,
3568    pub index_buffer_format: IndexBufferFormat,
3569    pub run_destructor: bool,
3570}
3571
3572impl fmt::Display for VertexBuffer {
3573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3574        write!(
3575            f,
3576            "VertexBuffer {{ buffer: {} (length: {}) }}",
3577            self.vertex_buffer_id, self.vertex_buffer_len
3578        )
3579    }
3580}
3581
3582impl_traits_for_gl_object!(VertexBuffer, vertex_buffer_id);
3583
3584impl Clone for VertexBuffer {
3585    fn clone(&self) -> Self {
3586        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
3587        Self {
3588            vao: self.vao.clone(),
3589            vertex_buffer_id: self.vertex_buffer_id,
3590            vertex_buffer_len: self.vertex_buffer_len,
3591            index_buffer_id: self.index_buffer_id,
3592            index_buffer_len: self.index_buffer_len,
3593            refcount: self.refcount,
3594            index_buffer_format: self.index_buffer_format,
3595            run_destructor: true,
3596        }
3597    }
3598}
3599
3600impl Drop for VertexBuffer {
3601    fn drop(&mut self) {
3602        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
3603        // freeing the refcount box twice (use-after-free) + double delete.
3604        if !self.run_destructor {
3605            return;
3606        }
3607        self.run_destructor = false;
3608        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3609        if copies == 1 {
3610            self.vao.vertex_layout = VertexLayout {
3611                fields: VertexAttributeVec::from_const_slice(&[]),
3612            };
3613            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3614            self.vao
3615                .gl_context
3616                .delete_buffers((&[self.vertex_buffer_id, self.index_buffer_id])[..].into());
3617        }
3618    }
3619}
3620
3621impl VertexBuffer {
3622    /// # Panics
3623    ///
3624    /// Panics if the GL driver failed to create the vertex-array/buffer objects
3625    /// (the returned id lists are empty).
3626    // OpenGL binding: buffer sizes / vertex counts passed to the gl API as
3627    // GLsizeiptr/GLint; values are GL-bounded.
3628    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
3629    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3630    pub fn new<T: VertexLayoutDescription>(
3631        gl_context: GlContextPtr,
3632        shader_program_id: GLuint,
3633        vertices: &[T],
3634        indices: &[u32],
3635        index_buffer_format: IndexBufferFormat,
3636    ) -> Self {
3637        use core::mem;
3638
3639        // Save the OpenGL state
3640        let mut current_vertex_array = [0_i32];
3641
3642        gl_context.get_integer_v(gl::VERTEX_ARRAY, (&mut current_vertex_array[..]).into());
3643
3644        let vertex_array_object = gl_context.gen_vertex_arrays(1);
3645        let vertex_array_object = vertex_array_object.get(0).unwrap();
3646
3647        let vertex_buffer_id = gl_context.gen_buffers(1);
3648        let vertex_buffer_id = vertex_buffer_id.get(0).unwrap();
3649
3650        let index_buffer_id = gl_context.gen_buffers(1);
3651        let index_buffer_id = index_buffer_id.get(0).unwrap();
3652
3653        gl_context.bind_vertex_array(*vertex_array_object);
3654
3655        // Upload vertex data to GPU
3656        gl_context.bind_buffer(gl::ARRAY_BUFFER, *vertex_buffer_id);
3657        gl_context.buffer_data_untyped(
3658            gl::ARRAY_BUFFER,
3659            size_of_val(vertices) as isize,
3660            GlVoidPtrConst {
3661                ptr: vertices.as_ptr() as *const core::ffi::c_void,
3662                run_destructor: true,
3663            },
3664            gl::STATIC_DRAW,
3665        );
3666
3667        // Generate the index buffer + upload data
3668        gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, *index_buffer_id);
3669        gl_context.buffer_data_untyped(
3670            gl::ELEMENT_ARRAY_BUFFER,
3671            size_of_val(indices) as isize,
3672            GlVoidPtrConst {
3673                ptr: indices.as_ptr() as *const core::ffi::c_void,
3674                run_destructor: true,
3675            },
3676            gl::STATIC_DRAW,
3677        );
3678
3679        let vertex_description = T::get_description();
3680        vertex_description.bind(&gl_context.ptr.ptr, shader_program_id);
3681
3682        // Reset the OpenGL state
3683        gl_context.bind_vertex_array(current_vertex_array[0] as u32);
3684
3685        Self::new_raw(
3686            *vertex_buffer_id,
3687            vertices.len(),
3688            VertexArrayObject::new(vertex_description, *vertex_array_object, gl_context),
3689            *index_buffer_id,
3690            indices.len(),
3691            index_buffer_format,
3692        )
3693    }
3694
3695    #[must_use] pub fn new_raw(
3696        vertex_buffer_id: GLuint,
3697        vertex_buffer_len: usize,
3698        vao: VertexArrayObject,
3699        index_buffer_id: GLuint,
3700        index_buffer_len: usize,
3701        index_buffer_format: IndexBufferFormat,
3702    ) -> Self {
3703        Self {
3704            vertex_buffer_id,
3705            vertex_buffer_len,
3706            vao,
3707            index_buffer_id,
3708            index_buffer_len,
3709            index_buffer_format,
3710            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3711            run_destructor: true,
3712        }
3713    }
3714}
3715
3716#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3717pub enum GlApiVersion {
3718    Gl { major: usize, minor: usize },
3719    GlEs { major: usize, minor: usize },
3720}
3721
3722impl GlApiVersion {
3723    /// Returns the OpenGL version of the context
3724    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
3725    #[must_use] pub fn get(gl_context: &GlContextPtr) -> Self {
3726        let mut major = [0];
3727        gl_context.get_integer_v(gl::MAJOR_VERSION, (&mut major[..]).into());
3728        let mut minor = [0];
3729        gl_context.get_integer_v(gl::MINOR_VERSION, (&mut minor[..]).into());
3730
3731        let major = major[0] as usize;
3732        let minor = minor[0] as usize;
3733
3734        match gl_context.get_type() {
3735            GlType::Gl => Self::Gl { major, minor },
3736            GlType::Gles => Self::GlEs { major, minor },
3737        }
3738    }
3739}
3740
3741#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3742#[repr(C)]
3743pub enum IndexBufferFormat {
3744    Points,
3745    Lines,
3746    LineStrip,
3747    Triangles,
3748    TriangleStrip,
3749    TriangleFan,
3750}
3751
3752impl IndexBufferFormat {
3753    /// Returns the `gl::TRIANGLE_STRIP` / `gl::POINTS`, etc.
3754    #[must_use] pub const fn get_gl_id(&self) -> GLuint {
3755        use self::IndexBufferFormat::{Points, Lines, LineStrip, Triangles, TriangleStrip, TriangleFan};
3756        match self {
3757            Points => gl::POINTS,
3758            Lines => gl::LINES,
3759            LineStrip => gl::LINE_STRIP,
3760            Triangles => gl::TRIANGLES,
3761            TriangleStrip => gl::TRIANGLE_STRIP,
3762            TriangleFan => gl::TRIANGLE_FAN,
3763        }
3764    }
3765}
3766
3767#[derive(Debug, Clone, PartialEq, PartialOrd)]
3768#[repr(C)]
3769pub struct Uniform {
3770    pub uniform_name: AzString,
3771    pub uniform_type: UniformType,
3772}
3773
3774impl Uniform {
3775    pub fn create<S: Into<AzString>>(name: S, uniform_type: UniformType) -> Self {
3776        Self {
3777            uniform_name: name.into(),
3778            uniform_type,
3779        }
3780    }
3781}
3782
3783#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
3784#[repr(C, u8)]
3785pub enum UniformType {
3786    Float(f32),
3787    FloatVec2([f32; 2]),
3788    FloatVec3([f32; 3]),
3789    FloatVec4([f32; 4]),
3790    Int(i32),
3791    IntVec2([i32; 2]),
3792    IntVec3([i32; 3]),
3793    IntVec4([i32; 4]),
3794    UnsignedInt(u32),
3795    UnsignedIntVec2([u32; 2]),
3796    UnsignedIntVec3([u32; 3]),
3797    UnsignedIntVec4([u32; 4]),
3798    Matrix2 {
3799        transpose: bool,
3800        matrix: [f32; 2 * 2],
3801    },
3802    Matrix3 {
3803        transpose: bool,
3804        matrix: [f32; 3 * 3],
3805    },
3806    Matrix4 {
3807        transpose: bool,
3808        matrix: [f32; 4 * 4],
3809    },
3810}
3811
3812impl UniformType {
3813    /// Set a specific uniform
3814    pub fn set(self, gl_context: &Rc<GenericGlContext>, location: GLint) {
3815        use self::UniformType::{Float, FloatVec2, FloatVec3, FloatVec4, Int, IntVec2, IntVec3, IntVec4, UnsignedInt, UnsignedIntVec2, UnsignedIntVec3, UnsignedIntVec4, Matrix2, Matrix3, Matrix4};
3816        match self {
3817            Float(r) => gl_context.uniform_1f(location, r),
3818            FloatVec2([r, g]) => gl_context.uniform_2f(location, r, g),
3819            FloatVec3([r, g, b]) => gl_context.uniform_3f(location, r, g, b),
3820            FloatVec4([r, g, b, a]) => gl_context.uniform_4f(location, r, g, b, a),
3821            Int(r) => gl_context.uniform_1i(location, r),
3822            IntVec2([r, g]) => gl_context.uniform_2i(location, r, g),
3823            IntVec3([r, g, b]) => gl_context.uniform_3i(location, r, g, b),
3824            IntVec4([r, g, b, a]) => gl_context.uniform_4i(location, r, g, b, a),
3825            UnsignedInt(r) => gl_context.uniform_1ui(location, r),
3826            UnsignedIntVec2([r, g]) => gl_context.uniform_2ui(location, r, g),
3827            UnsignedIntVec3([r, g, b]) => gl_context.uniform_3ui(location, r, g, b),
3828            UnsignedIntVec4([r, g, b, a]) => gl_context.uniform_4ui(location, r, g, b, a),
3829            Matrix2 { transpose, matrix } => {
3830                gl_context.uniform_matrix_2fv(location, transpose, &matrix[..]);
3831            }
3832            Matrix3 { transpose, matrix } => {
3833                gl_context.uniform_matrix_3fv(location, transpose, &matrix[..]);
3834            }
3835            Matrix4 { transpose, matrix } => {
3836                gl_context.uniform_matrix_4fv(location, transpose, &matrix[..]);
3837            }
3838        }
3839    }
3840}
3841
3842#[repr(C)]
3843pub struct GlShader {
3844    pub program_id: GLuint,
3845    pub gl_context: GlContextPtr,
3846    /// AUDIT: guards against a double-drop deleting the same GL program twice
3847    /// (`drop_in_place` run twice on a byte-copied struct). Set `true` on
3848    /// construction; the first drop clears it so a second drop is a no-op.
3849    pub run_destructor: bool,
3850}
3851
3852impl ::core::fmt::Display for GlShader {
3853    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3854        write!(f, "GlShader {{ program_id: {} }}", self.program_id)
3855    }
3856}
3857
3858impl_traits_for_gl_object!(GlShader, program_id);
3859
3860impl Drop for GlShader {
3861    fn drop(&mut self) {
3862        // AUDIT: mirror `GlContextPtr::drop` — a C-ABI double-drop would call
3863        // `delete_program` on the same id twice. The first drop clears the flag.
3864        if !self.run_destructor {
3865            return;
3866        }
3867        self.run_destructor = false;
3868        self.gl_context.delete_program(self.program_id);
3869    }
3870}
3871
3872#[repr(C)]
3873#[derive(Clone)]
3874pub struct VertexShaderCompileError {
3875    pub error_id: i32,
3876    pub info_log: AzString,
3877}
3878
3879impl_traits_for_gl_object!(VertexShaderCompileError, error_id);
3880
3881impl ::core::fmt::Display for VertexShaderCompileError {
3882    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3883        write!(f, "E{}: {}", self.error_id, self.info_log)
3884    }
3885}
3886
3887#[repr(C)]
3888#[derive(Clone)]
3889pub struct FragmentShaderCompileError {
3890    pub error_id: i32,
3891    pub info_log: AzString,
3892}
3893
3894impl_traits_for_gl_object!(FragmentShaderCompileError, error_id);
3895
3896impl ::core::fmt::Display for FragmentShaderCompileError {
3897    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3898        write!(f, "E{}: {}", self.error_id, self.info_log)
3899    }
3900}
3901
3902#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
3903pub enum GlShaderCompileError {
3904    Vertex(VertexShaderCompileError),
3905    Fragment(FragmentShaderCompileError),
3906}
3907
3908impl ::core::fmt::Display for GlShaderCompileError {
3909    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3910        use self::GlShaderCompileError::{Vertex, Fragment};
3911        match self {
3912            Vertex(vert_err) => write!(f, "Failed to compile vertex shader: {vert_err}"),
3913            Fragment(frag_err) => write!(f, "Failed to compile fragment shader: {frag_err}"),
3914        }
3915    }
3916}
3917
3918impl ::core::fmt::Debug for GlShaderCompileError {
3919    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3920        write!(f, "{self}")
3921    }
3922}
3923
3924#[repr(C)]
3925#[derive(Clone)]
3926pub struct GlShaderLinkError {
3927    pub error_id: i32,
3928    pub info_log: AzString,
3929}
3930
3931impl_traits_for_gl_object!(GlShaderLinkError, error_id);
3932
3933impl ::core::fmt::Display for GlShaderLinkError {
3934    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3935        write!(f, "E{}: {}", self.error_id, self.info_log)
3936    }
3937}
3938
3939#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
3940pub enum GlShaderCreateError {
3941    Compile(GlShaderCompileError),
3942    Link(GlShaderLinkError),
3943    NoShaderCompiler,
3944}
3945
3946impl ::core::fmt::Display for GlShaderCreateError {
3947    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3948        use self::GlShaderCreateError::{Compile, Link, NoShaderCompiler};
3949        match self {
3950            Compile(compile_err) => write!(f, "Shader compile error: {compile_err}"),
3951            Link(link_err) => write!(f, "Shader linking error: {link_err}"),
3952            NoShaderCompiler => {
3953                write!(f, "OpenGL implementation doesn't include a shader compiler")
3954            }
3955        }
3956    }
3957}
3958
3959impl ::core::fmt::Debug for GlShaderCreateError {
3960    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3961        write!(f, "{self}")
3962    }
3963}
3964
3965impl GlShader {
3966    /// Compiles and creates a new OpenGL shader, created from a vertex and a fragment shader
3967    /// string.
3968    ///
3969    /// If the shader fails to compile, the shader object gets automatically deleted, no cleanup
3970    /// necessary.
3971    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3972    /// # Errors
3973    ///
3974    /// Returns an error if the OpenGL implementation has no shader compiler, or if the vertex/fragment shader fails to compile or link.
3975    pub fn new(
3976        gl_context: &GlContextPtr,
3977        vertex_shader: &str,
3978        fragment_shader: &str,
3979    ) -> Result<Self, GlShaderCreateError> {
3980        // Check whether the OpenGL implementation supports a shader compiler...
3981        let mut shader_compiler_supported = [gl::FALSE as u8];
3982        gl_context.get_boolean_v(
3983            gl::SHADER_COMPILER,
3984            (&mut shader_compiler_supported[..]).into(),
3985        );
3986        if u32::from(shader_compiler_supported[0]) == gl::FALSE {
3987            // Implementation only supports binary shaders
3988            return Err(GlShaderCreateError::NoShaderCompiler);
3989        }
3990
3991        // Compile vertex shader
3992
3993        let vertex_shader_object = gl_context.create_shader(gl::VERTEX_SHADER);
3994        gl_context.shader_source(
3995            vertex_shader_object,
3996            vec![AzString::from(vertex_shader.to_string())].into(),
3997        );
3998        gl_context.compile_shader(vertex_shader_object);
3999
4000        if let Some(error_id) = get_gl_shader_error(gl_context, vertex_shader_object) {
4001            let info_log = gl_context.get_shader_info_log(vertex_shader_object);
4002            gl_context.delete_shader(vertex_shader_object);
4003            return Err(GlShaderCreateError::Compile(GlShaderCompileError::Vertex(
4004                VertexShaderCompileError {
4005                    error_id,
4006                    info_log,
4007                },
4008            )));
4009        }
4010
4011        // Compile fragment shader
4012
4013        let fragment_shader_object = gl_context.create_shader(gl::FRAGMENT_SHADER);
4014        gl_context.shader_source(
4015            fragment_shader_object,
4016            vec![AzString::from(fragment_shader.to_string())].into(),
4017        );
4018        gl_context.compile_shader(fragment_shader_object);
4019
4020        if let Some(error_id) = get_gl_shader_error(gl_context, fragment_shader_object) {
4021            let info_log = gl_context.get_shader_info_log(fragment_shader_object);
4022            gl_context.delete_shader(vertex_shader_object);
4023            gl_context.delete_shader(fragment_shader_object);
4024            return Err(GlShaderCreateError::Compile(
4025                GlShaderCompileError::Fragment(FragmentShaderCompileError {
4026                    error_id,
4027                    info_log,
4028                }),
4029            ));
4030        }
4031
4032        // Link program
4033
4034        let program_id = gl_context.create_program();
4035        gl_context.attach_shader(program_id, vertex_shader_object);
4036        gl_context.attach_shader(program_id, fragment_shader_object);
4037        gl_context.link_program(program_id);
4038
4039        if let Some(error_id) = get_gl_program_error(gl_context, program_id) {
4040            let info_log = gl_context.get_program_info_log(program_id);
4041            gl_context.delete_shader(vertex_shader_object);
4042            gl_context.delete_shader(fragment_shader_object);
4043            gl_context.delete_program(program_id);
4044            return Err(GlShaderCreateError::Link(GlShaderLinkError {
4045                error_id,
4046                info_log,
4047            }));
4048        }
4049
4050        gl_context.delete_shader(vertex_shader_object);
4051        gl_context.delete_shader(fragment_shader_object);
4052
4053        Ok(Self {
4054            program_id,
4055            gl_context: gl_context.clone(),
4056            run_destructor: true,
4057        })
4058    }
4059
4060    /// Draws vertex buffers, index buffers + uniforms to the texture
4061    ///
4062    /// # Panics
4063    ///
4064    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
4065    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4066    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
4067    pub fn draw(
4068        // shader to use for drawing
4069        shader_program_id: GLuint,
4070        // note: texture is &mut so the texture is reusable -
4071        texture: &mut Texture,
4072        // buffers + uniforms to draw
4073        buffers: &[(&VertexBuffer, &[Uniform])],
4074    ) {
4075        use alloc::collections::btree_map::BTreeMap;
4076
4077        const INDEX_TYPE: GLuint = gl::UNSIGNED_INT;
4078
4079        let texture_size = texture.size;
4080
4081        let gl_context = &texture.gl_context;
4082
4083        let saved = GlStateSave::save(gl_context);
4084
4085        // save draw()-specific state not covered by GlStateSave
4086        let mut current_blend_enabled = [0_u8];
4087        let mut current_primitive_restart_enabled = [0_u8];
4088        gl_context.get_boolean_v(gl::BLEND, (&mut current_blend_enabled[..]).into());
4089        gl_context.get_boolean_v(
4090            gl::PRIMITIVE_RESTART,
4091            (&mut current_primitive_restart_enabled[..]).into(),
4092        );
4093
4094        // 1. Create the framebuffer
4095        let framebuffers = gl_context.gen_framebuffers(1);
4096        let framebuffer_id = *framebuffers.get(0).unwrap();
4097        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
4098        // panic anywhere below (incl. `gen_renderbuffers().get(0).unwrap()`)
4099        // can't leak the FBO/RBO. Guard's Drop runs on unwind too.
4100        let mut fbo_rbo_guard = FboRboGuard {
4101            gl_context,
4102            framebuffer_id,
4103            renderbuffer_id: 0,
4104        };
4105        gl_context.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
4106
4107        let depthbuffers = gl_context.gen_renderbuffers(1);
4108        let depthbuffer_id = *depthbuffers.get(0).unwrap();
4109        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
4110
4111        gl_context.bind_texture(gl::TEXTURE_2D, texture.texture_id);
4112        gl_context.tex_image_2d(
4113            gl::TEXTURE_2D,
4114            0,
4115            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
4116            texture_size.width as i32,
4117            texture_size.height as i32,
4118            0,
4119            gl::RGBA, // gl::BGRA?
4120            gl::UNSIGNED_BYTE,
4121            None.into(),
4122        );
4123        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
4124        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
4125        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
4126        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
4127
4128        gl_context.bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
4129        gl_context.renderbuffer_storage(
4130            gl::RENDERBUFFER,
4131            gl::DEPTH_COMPONENT,
4132            texture_size.width as i32,
4133            texture_size.height as i32,
4134        );
4135        gl_context.framebuffer_renderbuffer(
4136            gl::FRAMEBUFFER,
4137            gl::DEPTH_ATTACHMENT,
4138            gl::RENDERBUFFER,
4139            depthbuffer_id,
4140        );
4141
4142        gl_context.framebuffer_texture_2d(
4143            gl::FRAMEBUFFER,
4144            gl::COLOR_ATTACHMENT0,
4145            gl::TEXTURE_2D,
4146            texture.texture_id,
4147            0,
4148        );
4149        gl_context.draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
4150
4151        #[cfg(feature = "std")]
4152        {
4153            let fb_check = gl_context.check_frame_buffer_status(gl::FRAMEBUFFER);
4154            match fb_check {
4155                gl::FRAMEBUFFER_COMPLETE => {}
4156                gl::FRAMEBUFFER_UNDEFINED => {
4157                    println!("GL_FRAMEBUFFER_UNDEFINED");
4158                }
4159                gl::FRAMEBUFFER_INCOMPLETE_ATTACHMENT => {
4160                    println!("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
4161                }
4162                gl::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT => {
4163                    println!("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
4164                }
4165                gl::FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER => {
4166                    println!("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
4167                }
4168                gl::FRAMEBUFFER_INCOMPLETE_READ_BUFFER => {
4169                    println!("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
4170                }
4171                gl::FRAMEBUFFER_UNSUPPORTED => {
4172                    println!("GL_FRAMEBUFFER_UNSUPPORTED");
4173                }
4174                gl::FRAMEBUFFER_INCOMPLETE_MULTISAMPLE => {
4175                    println!("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
4176                }
4177                gl::FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS => {
4178                    println!("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS");
4179                }
4180                o => {
4181                    println!("glFramebufferStatus returned unknown return code: {o}");
4182                }
4183            }
4184        }
4185
4186        gl_context.viewport(0, 0, texture_size.width as i32, texture_size.height as i32);
4187        gl_context.enable(gl::BLEND);
4188        // Use GL_PRIMITIVE_RESTART (OpenGL 3.1+) instead of
4189        // GL_PRIMITIVE_RESTART_FIXED_INDEX (4.3+) for macOS compatibility.
4190        gl_context.enable(gl::PRIMITIVE_RESTART);
4191        unsafe {
4192            let gl = gl_context.get();
4193            if !gl.glPrimitiveRestartIndex.is_null() {
4194                let func: extern "system" fn(u32) =
4195                    core::mem::transmute(gl.glPrimitiveRestartIndex);
4196                func(GL_RESTART_INDEX); // u32::MAX
4197            }
4198        }
4199        gl_context.disable(gl::MULTISAMPLE);
4200        gl_context.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); // TODO: enable / disable
4201        gl_context.use_program(shader_program_id);
4202
4203        // Avoid multiple calls to get_uniform_location by caching the uniform locations
4204        let mut uniform_locations: BTreeMap<AzString, i32> = BTreeMap::new();
4205        let mut max_uniform_len = 0;
4206        for (_, uniforms) in buffers {
4207            for uniform in *uniforms {
4208                if !uniform_locations.contains_key(&uniform.uniform_name) {
4209                    uniform_locations.insert(
4210                        uniform.uniform_name.clone(),
4211                        gl_context.get_uniform_location(
4212                            shader_program_id,
4213                            uniform.uniform_name.as_str(),
4214                        ),
4215                    );
4216                }
4217            }
4218            max_uniform_len = max_uniform_len.max(uniforms.len());
4219        }
4220        let mut current_uniforms = vec![None; max_uniform_len];
4221
4222        // Since the description of the vertex buffers is always the same,
4223        // only the first layer needs to bind its VAO
4224
4225        // Draw the actual layers
4226        for (vertex_index_buffer, uniforms) in buffers {
4227            gl_context.bind_vertex_array(vertex_index_buffer.vao.vao_id);
4228            gl_context.bind_buffer(gl::ARRAY_BUFFER, vertex_index_buffer.vertex_buffer_id);
4229            gl_context.bind_buffer(
4230                gl::ELEMENT_ARRAY_BUFFER,
4231                vertex_index_buffer.index_buffer_id,
4232            );
4233
4234            // Only set the uniform if the value has changed
4235            for (uniform_index, uniform) in uniforms.iter().enumerate() {
4236                if current_uniforms[uniform_index] != Some(uniform.uniform_type) {
4237                    let uniform_location = uniform_locations[&uniform.uniform_name];
4238                    uniform.uniform_type.set(gl_context.get(), uniform_location);
4239                    current_uniforms[uniform_index] = Some(uniform.uniform_type);
4240                }
4241            }
4242
4243            gl_context.draw_elements(
4244                vertex_index_buffer.index_buffer_format.get_gl_id(),
4245                vertex_index_buffer.index_buffer_len as i32,
4246                INDEX_TYPE,
4247                0,
4248            );
4249        }
4250
4251        // Reset draw()-specific state
4252        if u32::from(current_blend_enabled[0]) == gl::FALSE {
4253            gl_context.disable(gl::BLEND);
4254        }
4255        if u32::from(current_primitive_restart_enabled[0]) == gl::FALSE {
4256            gl_context.disable(gl::PRIMITIVE_RESTART);
4257        }
4258
4259        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
4260        // also runs on an unwinding panic) — reclaim them explicitly here so
4261        // the deletion order matches the original (delete before texture
4262        // metadata writes / after state restore).
4263        // Reset common GL state
4264        saved.restore(gl_context);
4265        drop(fbo_rbo_guard);
4266
4267        texture.format = RawImageFormat::RGBA8;
4268        texture.flags = TextureFlags {
4269            is_opaque: false,
4270            is_video_texture: false,
4271        };
4272    }
4273}
4274
4275#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4276fn get_gl_shader_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
4277    let mut err = [0];
4278    context.get_shader_iv(shader_object, gl::COMPILE_STATUS, (&mut err[..]).into());
4279    let err_code = err[0];
4280    if err_code == gl::TRUE as i32 {
4281        None
4282    } else {
4283        Some(err_code)
4284    }
4285}
4286
4287#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4288fn get_gl_program_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
4289    let mut err = [0];
4290    context.get_program_iv(shader_object, gl::LINK_STATUS, (&mut err[..]).into());
4291    let err_code = err[0];
4292    if err_code == gl::TRUE as i32 {
4293        None
4294    } else {
4295        Some(err_code)
4296    }
4297}
4298
4299#[cfg(test)]
4300mod audit_tests {
4301    use super::*;
4302
4303    // AUDIT: FFI slice/str accessors must return an empty slice (not form a
4304    // slice over a null/dangling ptr, which is UB) when handed a null or
4305    // zero-length descriptor from C.
4306    #[test]
4307    fn refstr_null_and_empty_is_empty_str() {
4308        let null = Refstr {
4309            ptr: core::ptr::null(),
4310            len: 0,
4311        };
4312        assert_eq!(null.as_str(), "");
4313
4314        // null ptr but nonzero len (garbage from FFI) must also not deref.
4315        let null_lenful = Refstr {
4316            ptr: core::ptr::null(),
4317            len: 5,
4318        };
4319        assert_eq!(null_lenful.as_str(), "");
4320
4321        let s = "hello";
4322        let good: Refstr = s.into();
4323        assert_eq!(good.as_str(), "hello");
4324    }
4325
4326    #[test]
4327    fn refstr_vec_ref_null_is_empty_slice() {
4328        let null = RefstrVecRef {
4329            ptr: core::ptr::null(),
4330            len: 3,
4331        };
4332        assert!(null.as_slice().is_empty());
4333    }
4334
4335    #[test]
4336    fn u8_vec_ref_null_is_empty_slice() {
4337        let null = U8VecRef {
4338            ptr: core::ptr::null(),
4339            len: 8,
4340        };
4341        assert!(null.as_slice().is_empty());
4342
4343        let data = [1u8, 2, 3];
4344        let good: U8VecRef = (&data[..]).into();
4345        assert_eq!(good.as_slice(), &[1, 2, 3]);
4346    }
4347}
4348
4349#[cfg(test)]
4350#[allow(clippy::float_cmp)] // exact f32 compares are the point: these assert bit-exact round-trips / lossy-cast values
4351mod autotest_generated {
4352    use super::*;
4353
4354    // ---------------------------------------------------------------------
4355    // Test scaffolding: a "null" GL context.
4356    //
4357    // Every field of `GenericGlContext` is a `*mut c_void` function pointer
4358    // (780 of them, no other field types), and every gl-context-loader method
4359    // null-checks its pointer and returns a default (0 / empty Vec / "") rather
4360    // than calling through. So an all-zero context is a SAFE no-op GL driver:
4361    // it lets us drive azul's whole wrapper surface off-GPU and assert how the
4362    // wrappers behave when the driver hands back degenerate values -- which is
4363    // exactly the "broken driver" path `is_gl_usable()` exists for.
4364    // ---------------------------------------------------------------------
4365    fn null_gl() -> Rc<GenericGlContext> {
4366        // SAFETY: `GenericGlContext` is `repr(C)` and every field is a raw
4367        // pointer, for which the all-zero bit pattern (null) is valid.
4368        Rc::new(unsafe { core::mem::zeroed::<GenericGlContext>() })
4369    }
4370
4371    fn null_ctx() -> GlContextPtr {
4372        GlContextPtr::new(RendererType::Software, null_gl())
4373    }
4374
4375    fn test_texture(texture_id: GLuint, width: u32, height: u32) -> Texture {
4376        Texture::create(
4377            texture_id,
4378            TextureFlags {
4379                is_opaque: false,
4380                is_video_texture: false,
4381            },
4382            PhysicalSizeU32 { width, height },
4383            ColorU {
4384                r: 1,
4385                g: 2,
4386                b: 3,
4387                a: 4,
4388            },
4389            null_ctx(),
4390            RawImageFormat::RGBA8,
4391        )
4392    }
4393
4394    const ALL_ATTRIB_TYPES: [VertexAttributeType; 5] = [
4395        VertexAttributeType::Float,
4396        VertexAttributeType::Double,
4397        VertexAttributeType::UnsignedByte,
4398        VertexAttributeType::UnsignedShort,
4399        VertexAttributeType::UnsignedInt,
4400    ];
4401
4402    const ALL_INDEX_FORMATS: [IndexBufferFormat; 6] = [
4403        IndexBufferFormat::Points,
4404        IndexBufferFormat::Lines,
4405        IndexBufferFormat::LineStrip,
4406        IndexBufferFormat::Triangles,
4407        IndexBufferFormat::TriangleStrip,
4408        IndexBufferFormat::TriangleFan,
4409    ];
4410
4411    // =====================================================================
4412    // Refstr / *VecRef / *VecRefMut -- FFI slice+str accessors
4413    // (parsers: malformed / huge / boundary / unicode)
4414    // =====================================================================
4415
4416    #[test]
4417    fn refstr_roundtrips_unicode_multibyte_and_combining_marks() {
4418        // Non-ASCII, emoji, and combining marks must survive byte-exactly:
4419        // `as_str` rebuilds the str via `from_utf8_unchecked` over the raw bytes.
4420        for s in [
4421            "\u{1F600}",
4422            "日本語のテキスト",
4423            "e\u{0301}\u{0328}combining",
4424            "\u{0}nul\u{0}embedded\u{0}",
4425            "\u{FFFD}\u{200B}zero-width",
4426        ] {
4427            let r: Refstr = s.into();
4428            assert_eq!(r.as_str(), s);
4429            assert_eq!(r.as_str().len(), s.len());
4430        }
4431    }
4432
4433    #[test]
4434    fn refstr_len_zero_over_nonempty_buffer_is_empty_not_dangling() {
4435        // len == 0 must short-circuit even when ptr is a perfectly valid buffer.
4436        let backing = "not empty";
4437        let r = Refstr {
4438            ptr: backing.as_ptr(),
4439            len: 0,
4440        };
4441        assert_eq!(r.as_str(), "");
4442    }
4443
4444    #[test]
4445    fn refstr_huge_input_does_not_hang() {
4446        // 1 MB single-token string: as_str is O(1) (no scan), so this must be instant.
4447        let huge = "x".repeat(1_000_000);
4448        let r: Refstr = huge.as_str().into();
4449        assert_eq!(r.as_str().len(), 1_000_000);
4450        assert_eq!(r.as_str(), huge.as_str());
4451    }
4452
4453    #[test]
4454    fn refstr_debug_on_null_does_not_panic() {
4455        let null = Refstr {
4456            ptr: core::ptr::null(),
4457            len: usize::MAX,
4458        };
4459        // Debug goes through as_str(); a null guard failure here would be UB, not a panic.
4460        assert_eq!(alloc::format!("{null:?}"), "\"\"");
4461    }
4462
4463    #[test]
4464    fn refstr_clone_preserves_ptr_and_len() {
4465        let s = "clone me";
4466        let r: Refstr = s.into();
4467        let c = r.clone();
4468        assert_eq!(c.as_str(), s);
4469        assert!(core::ptr::eq(c.ptr, r.ptr));
4470        assert_eq!(c.len, r.len);
4471    }
4472
4473    #[test]
4474    fn every_vec_ref_with_null_ptr_and_nonzero_len_is_empty() {
4475        // The FFI boundary can hand us a null ptr with a garbage (nonzero) len.
4476        // Forming a slice over null is UB, so every accessor must return empty.
4477        const GARBAGE_LEN: usize = usize::MAX;
4478
4479        assert!(RefstrVecRef {
4480            ptr: core::ptr::null(),
4481            len: GARBAGE_LEN
4482        }
4483        .as_slice()
4484        .is_empty());
4485        assert!(GLuintVecRef {
4486            ptr: core::ptr::null(),
4487            len: GARBAGE_LEN
4488        }
4489        .as_slice()
4490        .is_empty());
4491        assert!(GLenumVecRef {
4492            ptr: core::ptr::null(),
4493            len: GARBAGE_LEN
4494        }
4495        .as_slice()
4496        .is_empty());
4497        assert!(U8VecRef {
4498            ptr: core::ptr::null(),
4499            len: GARBAGE_LEN
4500        }
4501        .as_slice()
4502        .is_empty());
4503        assert!(F32VecRef {
4504            ptr: core::ptr::null(),
4505            len: GARBAGE_LEN
4506        }
4507        .as_slice()
4508        .is_empty());
4509        assert!(I32VecRef {
4510            ptr: core::ptr::null(),
4511            len: GARBAGE_LEN
4512        }
4513        .as_slice()
4514        .is_empty());
4515
4516        // ...and the same for the mutable variants, through BOTH accessors.
4517        let mut m64 = GLint64VecRefMut {
4518            ptr: core::ptr::null_mut(),
4519            len: GARBAGE_LEN,
4520        };
4521        assert!(m64.as_slice().is_empty());
4522        assert!(m64.as_mut_slice().is_empty());
4523
4524        let mut mf = GLfloatVecRefMut {
4525            ptr: core::ptr::null_mut(),
4526            len: GARBAGE_LEN,
4527        };
4528        assert!(mf.as_slice().is_empty());
4529        assert!(mf.as_mut_slice().is_empty());
4530
4531        let mut mi = GLintVecRefMut {
4532            ptr: core::ptr::null_mut(),
4533            len: GARBAGE_LEN,
4534        };
4535        assert!(mi.as_slice().is_empty());
4536        assert!(mi.as_mut_slice().is_empty());
4537
4538        let mut mb = GLbooleanVecRefMut {
4539            ptr: core::ptr::null_mut(),
4540            len: GARBAGE_LEN,
4541        };
4542        assert!(mb.as_slice().is_empty());
4543        assert!(mb.as_mut_slice().is_empty());
4544
4545        let mut mu8 = U8VecRefMut {
4546            ptr: core::ptr::null_mut(),
4547            len: GARBAGE_LEN,
4548        };
4549        assert!(mu8.as_slice().is_empty());
4550        assert!(mu8.as_mut_slice().is_empty());
4551    }
4552
4553    #[test]
4554    fn vec_refs_roundtrip_from_slice() {
4555        let u: [GLuint; 3] = [0, 1, u32::MAX];
4556        assert_eq!(GLuintVecRef::from(&u[..]).as_slice(), &u[..]);
4557
4558        let e: [GLenum; 2] = [gl::TRIANGLES, u32::MAX];
4559        assert_eq!(GLenumVecRef::from(&e[..]).as_slice(), &e[..]);
4560
4561        let b: [u8; 4] = [0, 127, 128, 255];
4562        assert_eq!(U8VecRef::from(&b[..]).as_slice(), &b[..]);
4563
4564        let i: [i32; 3] = [i32::MIN, 0, i32::MAX];
4565        assert_eq!(I32VecRef::from(&i[..]).as_slice(), &i[..]);
4566
4567        // Empty (but non-null) slices must also come back empty.
4568        let empty: [u8; 0] = [];
4569        assert!(U8VecRef::from(&empty[..]).as_slice().is_empty());
4570    }
4571
4572    #[test]
4573    fn f32_vec_ref_preserves_nan_inf_and_subnormals_bit_exactly() {
4574        // These are pointer casts, not conversions: NaN payloads and -0.0 must
4575        // survive bit-for-bit (a NaN-normalizing copy would be a real bug).
4576        let vals: [f32; 6] = [
4577            f32::NAN,
4578            f32::INFINITY,
4579            f32::NEG_INFINITY,
4580            -0.0,
4581            f32::MIN_POSITIVE / 2.0, // subnormal
4582            f32::MAX,
4583        ];
4584        let r = F32VecRef::from(&vals[..]);
4585        let got = r.as_slice();
4586        assert_eq!(got.len(), vals.len());
4587        for (g, v) in got.iter().zip(vals.iter()) {
4588            assert_eq!(g.to_bits(), v.to_bits());
4589        }
4590        assert!(got[0].is_nan());
4591        assert!(got[3].is_sign_negative());
4592    }
4593
4594    #[test]
4595    fn glint64_vec_ref_mut_handles_boundary_values() {
4596        let mut vals: [GLint64; 4] = [i64::MIN, -1, 0, i64::MAX];
4597        let mut r = GLint64VecRefMut::from(&mut vals[..]);
4598        assert_eq!(r.as_slice(), &[i64::MIN, -1, 0, i64::MAX]);
4599
4600        // Writes through as_mut_slice must land in the caller's buffer.
4601        r.as_mut_slice()[0] = i64::MAX;
4602        r.as_mut_slice()[3] = i64::MIN;
4603        assert_eq!(vals, [i64::MAX, -1, 0, i64::MIN]);
4604    }
4605
4606    #[test]
4607    fn mut_vec_refs_write_through_to_the_caller_buffer() {
4608        let mut floats: [GLfloat; 2] = [0.0, 0.0];
4609        GLfloatVecRefMut::from(&mut floats[..]).as_mut_slice()[1] = f32::NAN;
4610        assert!(floats[1].is_nan());
4611
4612        let mut ints: [GLint; 2] = [0, 0];
4613        GLintVecRefMut::from(&mut ints[..]).as_mut_slice()[0] = i32::MIN;
4614        assert_eq!(ints[0], i32::MIN);
4615
4616        let mut bools: [GLboolean; 2] = [0, 0];
4617        GLbooleanVecRefMut::from(&mut bools[..]).as_mut_slice()[1] = 255;
4618        assert_eq!(bools[1], 255);
4619
4620        let mut bytes: [u8; 3] = [1, 2, 3];
4621        U8VecRefMut::from(&mut bytes[..]).as_mut_slice()[2] = 0;
4622        assert_eq!(bytes, [1, 2, 0]);
4623    }
4624
4625    #[test]
4626    fn u8_vec_ref_ord_eq_hash_agree_with_the_underlying_slice() {
4627        use core::hash::{BuildHasher, Hasher};
4628
4629        use alloc::collections::BTreeSet;
4630
4631        let a = [1u8, 2, 3];
4632        let b = [1u8, 2, 4];
4633        let ra = U8VecRef::from(&a[..]);
4634        let rb = U8VecRef::from(&b[..]);
4635
4636        assert_eq!(ra, U8VecRef::from(&a[..]));
4637        assert!(ra < rb);
4638        assert_eq!(ra.cmp(&rb), a[..].cmp(&b[..]));
4639
4640        // A null ref must compare equal to an empty one (both are the empty slice).
4641        let null = U8VecRef {
4642            ptr: core::ptr::null(),
4643            len: 99,
4644        };
4645        let empty: [u8; 0] = [];
4646        assert_eq!(null, U8VecRef::from(&empty[..]));
4647
4648        // Hash must be consistent with Eq (equal values -> equal hashes).
4649        fn hash_of(v: &U8VecRef) -> u64 {
4650            
4651            
4652            core::hash::BuildHasherDefault::<TestHasher>::default().hash_one(v)
4653        }
4654        #[derive(Default)]
4655        struct TestHasher(u64);
4656        impl Hasher for TestHasher {
4657            fn finish(&self) -> u64 {
4658                self.0
4659            }
4660            fn write(&mut self, bytes: &[u8]) {
4661                for b in bytes {
4662                    self.0 = self.0.wrapping_mul(31).wrapping_add(u64::from(*b));
4663                }
4664            }
4665        }
4666        assert_eq!(hash_of(&ra), hash_of(&U8VecRef::from(&a[..])));
4667        assert_eq!(hash_of(&null), hash_of(&U8VecRef::from(&empty[..])));
4668
4669        // Ord must be a total order good enough for a BTreeSet.
4670        let mut set = BTreeSet::new();
4671        set.insert(ra.clone());
4672        set.insert(U8VecRef::from(&a[..]));
4673        set.insert(rb);
4674        assert_eq!(set.len(), 2);
4675    }
4676
4677    #[test]
4678    fn refstr_vec_ref_roundtrips_and_maps_back_to_strs() {
4679        let strs = ["", "a", "\u{1F600}"];
4680        let refstrs: Vec<Refstr> = strs.iter().map(|s| Refstr::from(*s)).collect();
4681        let vec_ref = RefstrVecRef::from(&refstrs[..]);
4682        let got: Vec<&str> = vec_ref.as_slice().iter().map(Refstr::as_str).collect();
4683        assert_eq!(got, strs);
4684    }
4685
4686    // =====================================================================
4687    // GLsyncPtr (constructor + round-trip)
4688    // =====================================================================
4689
4690    #[test]
4691    fn glsync_ptr_roundtrips_null_and_nonnull() {
4692        let null = GLsyncPtr::new(core::ptr::null());
4693        assert!(null.clone().get().is_null());
4694        assert_eq!(alloc::format!("{null:?}"), "0x0");
4695
4696        // A non-null (never dereferenced) sentinel must round-trip identically.
4697        let sentinel = usize::MAX as *const c_void;
4698        let p = GLsyncPtr::new(sentinel);
4699        assert_eq!(p.clone().get() as usize, usize::MAX);
4700        assert!(p.run_destructor);
4701    }
4702
4703    // =====================================================================
4704    // shader_with_glsl_version (parser: the `#version` line swap)
4705    // =====================================================================
4706
4707    #[cfg(feature = "std")]
4708    #[test]
4709    fn shader_with_glsl_version_replaces_only_the_first_line() {
4710        let src = b"#version 150\nbody line 1\nbody line 2";
4711        let out = shader_with_glsl_version(src, b"#version 300 es\n");
4712        assert_eq!(out, b"#version 300 es\nbody line 1\nbody line 2".to_vec());
4713    }
4714
4715    #[cfg(feature = "std")]
4716    #[test]
4717    fn shader_with_glsl_version_empty_src_yields_just_the_version_line() {
4718        // No newline in src -> body_start = 0 (the map_or default), so the whole
4719        // (empty) src is kept and only the version line is prepended.
4720        assert_eq!(
4721            shader_with_glsl_version(b"", b"#version 150\n"),
4722            b"#version 150\n".to_vec()
4723        );
4724    }
4725
4726    #[cfg(feature = "std")]
4727    #[test]
4728    fn shader_with_glsl_version_src_without_newline_is_kept_whole() {
4729        // A src with NO newline has no first line to strip: body_start stays 0,
4730        // so the version line is prepended and nothing is lost.
4731        let out = shader_with_glsl_version(b"void main(){}", b"#version 150\n");
4732        assert_eq!(out, b"#version 150\nvoid main(){}".to_vec());
4733    }
4734
4735    #[cfg(feature = "std")]
4736    #[test]
4737    fn shader_with_glsl_version_leading_newline_drops_only_that_newline() {
4738        let out = shader_with_glsl_version(b"\nrest", b"V\n");
4739        assert_eq!(out, b"V\nrest".to_vec());
4740    }
4741
4742    #[cfg(feature = "std")]
4743    #[test]
4744    fn shader_with_glsl_version_empty_version_line_still_strips_first_line() {
4745        let out = shader_with_glsl_version(b"#version 150\nbody", b"");
4746        assert_eq!(out, b"body".to_vec());
4747
4748        // Both empty -> empty, no panic.
4749        assert!(shader_with_glsl_version(b"", b"").is_empty());
4750    }
4751
4752    #[cfg(feature = "std")]
4753    #[test]
4754    fn shader_with_glsl_version_is_byte_exact_on_invalid_utf8_and_nul_bytes() {
4755        // Shader sources are bytes, not str: invalid UTF-8 / NULs must pass
4756        // through untouched (this is fed straight to glShaderSource).
4757        let src = b"#version 150\n\xFF\xFE\x00\x80body";
4758        let out = shader_with_glsl_version(src, b"#version 100\n");
4759        assert_eq!(out, b"#version 100\n\xFF\xFE\x00\x80body".to_vec());
4760
4761        // A src that is ONLY invalid bytes with no newline is kept whole.
4762        let out2 = shader_with_glsl_version(&[0xFF, 0xFE, 0x00], b"V\n");
4763        assert_eq!(out2, vec![b'V', b'\n', 0xFF, 0xFE, 0x00]);
4764    }
4765
4766    #[cfg(feature = "std")]
4767    #[test]
4768    fn shader_with_glsl_version_handles_a_1mb_source_without_hanging() {
4769        let mut src = b"#version 150\n".to_vec();
4770        src.resize(src.len() + 1_000_000, b'x');
4771        let out = shader_with_glsl_version(&src, b"#version 300 es\n");
4772        assert_eq!(out.len(), b"#version 300 es\n".len() + 1_000_000);
4773        assert!(out.starts_with(b"#version 300 es\n"));
4774        assert!(out.ends_with(b"xxxx"));
4775    }
4776
4777    // =====================================================================
4778    // glsl_version_candidates (invariants the version probe relies on)
4779    // =====================================================================
4780
4781    #[test]
4782    fn glsl_version_candidates_are_wellformed_and_distinct() {
4783        for gl_type in [GlType::Gl, GlType::Gles] {
4784            let candidates = glsl_version_candidates(gl_type);
4785            assert!(
4786                !candidates.is_empty(),
4787                "{gl_type:?} must have at least one candidate or the probe can never succeed"
4788            );
4789            for c in candidates {
4790                // GlContextPtr::new parses these back out with
4791                // `trim().trim_start_matches("#version ")`, which requires
4792                // valid UTF-8, the exact prefix, and a trailing newline.
4793                let s = core::str::from_utf8(c).expect("candidate must be valid UTF-8");
4794                assert!(s.starts_with("#version "), "{s:?}");
4795                assert!(s.ends_with('\n'), "{s:?}");
4796                let parsed = s.trim().trim_start_matches("#version ");
4797                assert!(!parsed.is_empty(), "{s:?} must parse to a nonempty version");
4798            }
4799            // No duplicates: a repeated candidate would just re-probe a known failure.
4800            for (i, a) in candidates.iter().enumerate() {
4801                for b in candidates.iter().skip(i + 1) {
4802                    assert_ne!(a, b, "duplicate candidate in {gl_type:?}");
4803                }
4804            }
4805        }
4806
4807        // The GL and GLES lists must be disjoint (a GLES driver rejects `#version 150`).
4808        for g in glsl_version_candidates(GlType::Gl) {
4809            assert!(!glsl_version_candidates(GlType::Gles).contains(g));
4810        }
4811
4812        // Documented examples from the API docs: "150" (GL) and "300 es" (GLES).
4813        let first_gl = core::str::from_utf8(glsl_version_candidates(GlType::Gl)[0]).unwrap();
4814        assert_eq!(first_gl.trim().trim_start_matches("#version "), "150");
4815        let first_es = core::str::from_utf8(glsl_version_candidates(GlType::Gles)[0]).unwrap();
4816        assert_eq!(first_es.trim().trim_start_matches("#version "), "300 es");
4817    }
4818
4819    #[test]
4820    fn gl_type_from_context_gl_type_is_total() {
4821        assert_eq!(GlType::from(GlContextGlType::Gl), GlType::Gl);
4822        assert_eq!(GlType::from(GlContextGlType::GlEs), GlType::Gles);
4823    }
4824
4825    // =====================================================================
4826    // GlContextPtr -- construction + the documented "unusable driver" fallback
4827    // =====================================================================
4828
4829    #[test]
4830    fn gl_context_ptr_software_is_never_usable_and_has_no_shaders() {
4831        // Documented: "Always `false` for a Software context (which never
4832        // compiles these shaders)".
4833        let ctx = GlContextPtr::new(RendererType::Software, null_gl());
4834        assert!(!ctx.is_gl_usable());
4835        assert_eq!(ctx.get_svg_shader(), 0);
4836        assert_eq!(ctx.get_brush_shader(), 0);
4837        assert_eq!(ctx.get_fxaa_shader(), 0);
4838        assert_eq!(ctx.get_usable_glsl_version().as_str(), "");
4839        assert_eq!(ctx.renderer_type, RendererType::Software);
4840    }
4841
4842    #[test]
4843    fn gl_context_ptr_hardware_with_broken_driver_falls_back_instead_of_panicking() {
4844        // THE contract `is_gl_usable()` exists for: context creation "succeeds"
4845        // but the driver compiles nothing. The probe must try every candidate
4846        // version, fail them all, and leave every program id at 0 -- so the
4847        // caller can fall back to CPU rendering -- rather than panicking or
4848        // handing out a garbage program id.
4849        let ctx = GlContextPtr::new(RendererType::Hardware, null_gl());
4850        assert!(
4851            !ctx.is_gl_usable(),
4852            "a driver that compiles nothing must report is_gl_usable() == false"
4853        );
4854        assert_eq!(ctx.get_svg_shader(), 0);
4855        assert_eq!(ctx.get_brush_shader(), 0);
4856        assert_eq!(ctx.get_fxaa_shader(), 0);
4857        assert_eq!(
4858            ctx.get_usable_glsl_version().as_str(),
4859            "",
4860            "glsl_version must be empty when the context is unusable"
4861        );
4862    }
4863
4864    #[test]
4865    fn gl_context_ptr_get_type_defaults_to_desktop_gl_on_an_empty_version_string() {
4866        // get_type() sniffs the GL_VERSION string for "OpenGL ES"; a driver that
4867        // returns nothing must deterministically fall out as desktop Gl.
4868        assert_eq!(null_ctx().get_type(), GlType::Gl);
4869    }
4870
4871    #[test]
4872    fn gl_context_ptr_clone_is_eq_but_distinct_contexts_are_not() {
4873        // Eq/Ord are identity-based (`as_usize` = the inner Rc address), so a
4874        // clone must compare equal and two independently created contexts must not.
4875        let a = null_ctx();
4876        let clone = a.clone();
4877        assert_eq!(a, clone);
4878        assert_eq!(a.cmp(&clone), core::cmp::Ordering::Equal);
4879        assert_eq!(a.partial_cmp(&clone), Some(core::cmp::Ordering::Equal));
4880
4881        let b = null_ctx();
4882        assert_ne!(a, b);
4883        // Ord must be antisymmetric and consistent with PartialOrd.
4884        assert_eq!(a.cmp(&b), a.partial_cmp(&b).unwrap());
4885        assert_eq!(a.cmp(&b).reverse(), b.cmp(&a));
4886
4887        // The clone must point at the same underlying GL context.
4888        assert!(Rc::ptr_eq(a.get(), clone.get()));
4889        assert!(!Rc::ptr_eq(a.get(), b.get()));
4890    }
4891
4892    #[test]
4893    fn gl_context_ptr_gen_family_returns_empty_for_every_n_including_negatives() {
4894        // A driver that generates nothing returns an EMPTY list. Callers index
4895        // into this ([0] / .get(0).unwrap()), so the wrappers must at least not
4896        // panic on the way out -- and must not choke on a negative/extreme n.
4897        let ctx = null_ctx();
4898        for n in [0, 1, -1, i32::MIN, i32::MAX] {
4899            assert!(ctx.gen_buffers(n).as_slice().is_empty(), "gen_buffers({n})");
4900            assert!(ctx.gen_textures(n).as_slice().is_empty(), "gen_textures({n})");
4901            assert!(
4902                ctx.gen_framebuffers(n).as_slice().is_empty(),
4903                "gen_framebuffers({n})"
4904            );
4905            assert!(
4906                ctx.gen_renderbuffers(n).as_slice().is_empty(),
4907                "gen_renderbuffers({n})"
4908            );
4909            assert!(
4910                ctx.gen_vertex_arrays(n).as_slice().is_empty(),
4911                "gen_vertex_arrays({n})"
4912            );
4913            assert!(ctx.gen_queries(n).as_slice().is_empty(), "gen_queries({n})");
4914        }
4915    }
4916
4917    #[test]
4918    fn gl_context_ptr_integer_extremes_do_not_panic() {
4919        // Offsets/sizes/limits at the boundary of their integer types. These are
4920        // passed straight through to GL, so the wrapper must not do arithmetic
4921        // that overflows on the way.
4922        let ctx = null_ctx();
4923        let data = [0u8; 4];
4924        let void_ptr = || GlVoidPtrConst {
4925            ptr: data.as_ptr().cast(),
4926            run_destructor: false,
4927        };
4928
4929        for offset in [0isize, -1, isize::MIN, isize::MAX] {
4930            for size in [0isize, -1, isize::MIN, isize::MAX] {
4931                ctx.buffer_sub_data_untyped(gl::ARRAY_BUFFER, offset, size, void_ptr());
4932                let _ = ctx.map_buffer_range(gl::ARRAY_BUFFER, offset, size, 0);
4933            }
4934        }
4935        ctx.buffer_data_untyped(gl::ARRAY_BUFFER, isize::MIN, void_ptr(), gl::STATIC_DRAW);
4936
4937        // usize offset at the extreme (reinterpreted as a byte offset pointer).
4938        for offset in [0usize, 1, usize::MAX] {
4939            ctx.tex_sub_image_2d_pbo(
4940                gl::TEXTURE_2D,
4941                0,
4942                0,
4943                0,
4944                1,
4945                1,
4946                gl::RGBA,
4947                gl::UNSIGNED_BYTE,
4948                offset,
4949            );
4950        }
4951
4952        ctx.pixel_store_i(gl::PACK_ALIGNMENT, i32::MIN);
4953        ctx.pixel_store_i(gl::PACK_ALIGNMENT, i32::MAX);
4954        let _ = ctx.unmap_buffer(gl::ARRAY_BUFFER);
4955    }
4956
4957    #[test]
4958    fn gl_context_ptr_float_extremes_do_not_panic() {
4959        // NaN / inf / subnormal floats must pass through the f32 wrappers untouched.
4960        let ctx = null_ctx();
4961        for v in [
4962            0.0,
4963            1.0,
4964            -1.0,
4965            f32::NAN,
4966            f32::INFINITY,
4967            f32::NEG_INFINITY,
4968            f32::MIN,
4969            f32::MAX,
4970        ] {
4971            ctx.sample_coverage(v, false);
4972            ctx.sample_coverage(v, true);
4973            ctx.polygon_offset(v, v);
4974        }
4975    }
4976
4977    #[test]
4978    fn gl_context_ptr_shader_source_handles_empty_unicode_and_embedded_nuls() {
4979        // shader_source NUL-terminates each string itself; an already-embedded
4980        // NUL or multibyte UTF-8 must not panic on the way through.
4981        let ctx = null_ctx();
4982        let strings: StringVec = vec![
4983            AzString::from(String::new()),
4984            AzString::from("\u{1F600} emoji".to_string()),
4985            AzString::from("has\u{0}nul".to_string()),
4986            AzString::from("x".repeat(100_000)),
4987        ]
4988        .into();
4989        ctx.shader_source(0, strings);
4990
4991        // An empty list of sources must also be fine.
4992        ctx.shader_source(u32::MAX, Vec::<AzString>::new().into());
4993    }
4994
4995    #[test]
4996    fn gl_context_ptr_read_pixels_sizes_the_buffer_from_the_dimensions() {
4997        // NOTE: only SAFE (small, positive) dimensions are exercised here.
4998        // Negative dimensions are a live hazard in this path -- see the report:
4999        // gl-context-loader's read_pixels does
5000        //   `vec![0; width as usize * height as usize * bit_depth]`
5001        // BEFORE its null-pointer check, so a negative width sign-extends to
5002        // ~usize::MAX and the multiply overflows (debug) / requests an
5003        // exabyte-scale allocation (release). Deliberately not provoked.
5004        let ctx = null_ctx();
5005        let px = ctx.read_pixels(0, 0, 2, 3, gl::RGBA, gl::UNSIGNED_BYTE);
5006        assert_eq!(px.len(), 2 * 3 * 4, "RGBA/UNSIGNED_BYTE = 4 bytes per pixel");
5007
5008        // A zero-size read is the degenerate-but-safe case.
5009        assert_eq!(
5010            ctx.read_pixels(0, 0, 0, 0, gl::RGBA, gl::UNSIGNED_BYTE).len(),
5011            0
5012        );
5013    }
5014
5015    #[test]
5016    fn gl_context_ptr_read_pixels_into_buffer_accepts_null_and_undersized_targets() {
5017        // The destination is caller-owned here (no allocation from the dims), so
5018        // a null / empty / undersized target must simply no-op.
5019        let ctx = null_ctx();
5020        ctx.read_pixels_into_buffer(
5021            0,
5022            0,
5023            4,
5024            4,
5025            gl::RGBA,
5026            gl::UNSIGNED_BYTE,
5027            U8VecRefMut {
5028                ptr: core::ptr::null_mut(),
5029                len: 64,
5030            },
5031        );
5032
5033        let mut small = [0u8; 1];
5034        ctx.read_pixels_into_buffer(
5035            0,
5036            0,
5037            4,
5038            4,
5039            gl::RGBA,
5040            gl::UNSIGNED_BYTE,
5041            (&mut small[..]).into(),
5042        );
5043        assert_eq!(small, [0u8; 1]);
5044    }
5045
5046    #[test]
5047    fn gl_context_ptr_uniform_getters_accept_null_result_buffers() {
5048        let ctx = null_ctx();
5049        ctx.get_uniform_iv(
5050            0,
5051            -1,
5052            GLintVecRefMut {
5053                ptr: core::ptr::null_mut(),
5054                len: 16,
5055            },
5056        );
5057        ctx.get_uniform_fv(
5058            0,
5059            i32::MIN,
5060            GLfloatVecRefMut {
5061                ptr: core::ptr::null_mut(),
5062                len: 16,
5063            },
5064        );
5065
5066        // ...and real buffers, at extreme locations.
5067        let mut ints = [0i32; 2];
5068        ctx.get_uniform_iv(u32::MAX, i32::MAX, (&mut ints[..]).into());
5069        let mut floats = [0f32; 2];
5070        ctx.get_uniform_fv(u32::MAX, i32::MAX, (&mut floats[..]).into());
5071    }
5072
5073    #[test]
5074    fn gl_context_ptr_get_uniform_indices_handles_empty_and_null_name_lists() {
5075        let ctx = null_ctx();
5076        let empty: &[Refstr] = &[];
5077        assert!(ctx
5078            .get_uniform_indices(0, empty.into())
5079            .as_slice()
5080            .is_empty());
5081        assert!(ctx
5082            .get_uniform_indices(
5083                0,
5084                RefstrVecRef {
5085                    ptr: core::ptr::null(),
5086                    len: 4,
5087                },
5088            )
5089            .as_slice()
5090            .is_empty());
5091    }
5092
5093    #[test]
5094    fn gl_context_ptr_delete_family_accepts_empty_and_null_id_lists() {
5095        // Deleting nothing must be a no-op, not an out-of-bounds read.
5096        let ctx = null_ctx();
5097        let empty: &[GLuint] = &[];
5098        ctx.delete_buffers(empty.into());
5099        ctx.delete_textures(empty.into());
5100        ctx.delete_framebuffers(empty.into());
5101        ctx.delete_renderbuffers(empty.into());
5102        ctx.delete_vertex_arrays(empty.into());
5103        ctx.delete_queries(empty.into());
5104
5105        let null = || GLuintVecRef {
5106            ptr: core::ptr::null(),
5107            len: 7,
5108        };
5109        ctx.delete_buffers(null());
5110        ctx.delete_textures(null());
5111        ctx.delete_framebuffers(null());
5112        ctx.delete_renderbuffers(null());
5113        ctx.delete_vertex_arrays(null());
5114        ctx.delete_queries(null());
5115
5116        // A real (bogus) id list, incl. 0 and u32::MAX, must also be fine.
5117        let ids: &[GLuint] = &[0, 1, u32::MAX];
5118        ctx.delete_buffers(ids.into());
5119        ctx.delete_textures(ids.into());
5120    }
5121
5122    #[test]
5123    fn gl_context_ptr_draw_buffers_accepts_an_empty_list() {
5124        let ctx = null_ctx();
5125        let empty: &[GLenum] = &[];
5126        ctx.draw_buffers(empty.into());
5127        ctx.draw_buffers(
5128            GLenumVecRef {
5129                ptr: core::ptr::null(),
5130                len: 3,
5131            },
5132        );
5133    }
5134
5135    // =====================================================================
5136    // VertexAttributeType / VertexAttribute / VertexLayout (numeric + invariants)
5137    // =====================================================================
5138
5139    #[test]
5140    fn vertex_attribute_type_mem_size_matches_the_rust_type_it_names() {
5141        assert_eq!(
5142            VertexAttributeType::Float.get_mem_size(),
5143            core::mem::size_of::<f32>()
5144        );
5145        assert_eq!(
5146            VertexAttributeType::Double.get_mem_size(),
5147            core::mem::size_of::<f64>()
5148        );
5149        assert_eq!(
5150            VertexAttributeType::UnsignedByte.get_mem_size(),
5151            core::mem::size_of::<u8>()
5152        );
5153        assert_eq!(
5154            VertexAttributeType::UnsignedShort.get_mem_size(),
5155            core::mem::size_of::<u16>()
5156        );
5157        assert_eq!(
5158            VertexAttributeType::UnsignedInt.get_mem_size(),
5159            core::mem::size_of::<u32>()
5160        );
5161
5162        // Every size must be nonzero -- a 0 would make get_stride() collapse to 0
5163        // and silently produce a degenerate vertex layout.
5164        for t in ALL_ATTRIB_TYPES {
5165            assert!(t.get_mem_size() > 0, "{t:?}");
5166        }
5167    }
5168
5169    #[test]
5170    fn vertex_attribute_type_gl_ids_are_distinct_and_nonzero() {
5171        for (i, a) in ALL_ATTRIB_TYPES.iter().enumerate() {
5172            assert_ne!(a.get_gl_id(), 0, "{a:?} maps to the GL 'no type' id 0");
5173            for b in ALL_ATTRIB_TYPES.iter().skip(i + 1) {
5174                assert_ne!(
5175                    a.get_gl_id(),
5176                    b.get_gl_id(),
5177                    "{a:?} and {b:?} share a GL id -- one of them would upload as the wrong type"
5178                );
5179            }
5180        }
5181        assert_eq!(VertexAttributeType::Float.get_gl_id(), gl::FLOAT);
5182        assert_eq!(
5183            VertexAttributeType::UnsignedByte.get_gl_id(),
5184            gl::UNSIGNED_BYTE
5185        );
5186    }
5187
5188    #[test]
5189    fn vertex_attribute_get_stride_at_zero_and_at_the_overflow_boundary() {
5190        let attr = |ty, item_count| VertexAttribute {
5191            va_name: AzString::from_const_str("vAttrXY"),
5192            layout_location: OptionUsize::None,
5193            attribute_type: ty,
5194            item_count,
5195        };
5196
5197        // Zero items -> zero stride.
5198        for t in ALL_ATTRIB_TYPES {
5199            assert_eq!(attr(t, 0).get_stride(), 0, "{t:?}");
5200        }
5201
5202        // Exact multiplication for representative counts.
5203        assert_eq!(attr(VertexAttributeType::Float, 2).get_stride(), 8);
5204        assert_eq!(attr(VertexAttributeType::Double, 4).get_stride(), 32);
5205        assert_eq!(attr(VertexAttributeType::UnsignedByte, 3).get_stride(), 3);
5206
5207        // The LARGEST item_count that does not overflow `mem_size * item_count`.
5208        // (NOTE: get_stride() is a plain `*`, so an item_count above this bound
5209        // overflow-panics in debug / wraps in release -- see the report.)
5210        for t in ALL_ATTRIB_TYPES {
5211            let max_items = usize::MAX / t.get_mem_size();
5212            assert_eq!(
5213                attr(t, max_items).get_stride(),
5214                max_items * t.get_mem_size(),
5215                "{t:?} at the overflow boundary"
5216            );
5217        }
5218    }
5219
5220    #[test]
5221    fn vertex_layout_stride_is_the_sum_of_its_fields() {
5222        let attr = |name: &str, ty, item_count| VertexAttribute {
5223            va_name: AzString::from(name.to_string()),
5224            layout_location: OptionUsize::None,
5225            attribute_type: ty,
5226            item_count,
5227        };
5228
5229        // Empty layout: zero fields, zero total stride.
5230        let empty = VertexLayout {
5231            fields: VertexAttributeVec::from_const_slice(&[]),
5232        };
5233        assert_eq!(
5234            empty.fields.iter().map(VertexAttribute::get_stride).sum::<usize>(),
5235            0
5236        );
5237
5238        // vec2<f32> + vec4<u8> = 8 + 4 = 12 bytes per vertex.
5239        let layout = VertexLayout {
5240            fields: vec![
5241                attr("vAttrXY", VertexAttributeType::Float, 2),
5242                attr("vColor", VertexAttributeType::UnsignedByte, 4),
5243            ]
5244            .into(),
5245        };
5246        let total: usize = layout.fields.iter().map(VertexAttribute::get_stride).sum();
5247        assert_eq!(total, 12);
5248
5249        // Equality/Hash are structural, so an identical layout compares equal.
5250        assert_eq!(layout, layout.clone());
5251    }
5252
5253    #[test]
5254    fn index_buffer_format_gl_ids_are_distinct() {
5255        // A collision here would silently draw the wrong primitive.
5256        for (i, a) in ALL_INDEX_FORMATS.iter().enumerate() {
5257            for b in ALL_INDEX_FORMATS.iter().skip(i + 1) {
5258                assert_ne!(a.get_gl_id(), b.get_gl_id(), "{a:?} vs {b:?}");
5259            }
5260        }
5261        assert_eq!(IndexBufferFormat::Points.get_gl_id(), gl::POINTS);
5262        assert_eq!(
5263            IndexBufferFormat::TriangleStrip.get_gl_id(),
5264            gl::TRIANGLE_STRIP
5265        );
5266    }
5267
5268    // =====================================================================
5269    // Uniform / UniformType (NaN semantics feed GlShader::draw's dedupe)
5270    // =====================================================================
5271
5272    #[test]
5273    fn uniform_type_nan_never_equals_itself_so_draw_always_reuploads_it() {
5274        // GlShader::draw skips re-setting a uniform when
5275        // `current_uniforms[i] != Some(uniform.uniform_type)`. With a NaN payload
5276        // that comparison is ALWAYS unequal, so a NaN uniform is re-uploaded on
5277        // every buffer -- correct (never stale), just not deduped. Pin it.
5278        let nan = UniformType::Float(f32::NAN);
5279        assert_ne!(nan, UniformType::Float(f32::NAN));
5280        assert_ne!(Some(nan), Some(nan));
5281
5282        let nan_vec = UniformType::FloatVec4([f32::NAN, 0.0, 0.0, 0.0]);
5283        assert_ne!(nan_vec, nan_vec);
5284
5285        // The flip side: +0.0 and -0.0 compare EQUAL, so a -0.0 -> +0.0 change is
5286        // deduped away and never re-uploaded. Harmless for a uniform, but pinned
5287        // so a change in semantics is visible.
5288        assert_eq!(UniformType::Float(0.0), UniformType::Float(-0.0));
5289
5290        // Integer uniforms have no such wrinkle: they dedupe exactly.
5291        assert_eq!(UniformType::Int(i32::MIN), UniformType::Int(i32::MIN));
5292        assert_ne!(UniformType::Int(0), UniformType::UnsignedInt(0));
5293    }
5294
5295    #[test]
5296    fn uniform_type_matrix_transpose_flag_is_part_of_its_identity() {
5297        let m = [0.0f32; 4];
5298        assert_ne!(
5299            UniformType::Matrix2 {
5300                transpose: false,
5301                matrix: m
5302            },
5303            UniformType::Matrix2 {
5304                transpose: true,
5305                matrix: m
5306            },
5307        );
5308        // Same payload, different arity -> different uniforms.
5309        assert_ne!(
5310            UniformType::FloatVec2([1.0, 2.0]),
5311            UniformType::IntVec2([1, 2])
5312        );
5313    }
5314
5315    #[test]
5316    fn uniform_create_preserves_empty_unicode_and_huge_names() {
5317        let huge = "n".repeat(100_000);
5318        for name in ["", "u_color", "\u{1F600}", "e\u{0301}", huge.as_str()] {
5319            let u = Uniform::create(name.to_string(), UniformType::Int(0));
5320            assert_eq!(u.uniform_name.as_str(), name);
5321        }
5322    }
5323
5324    // =====================================================================
5325    // GlShader -- the no-shader-compiler error path
5326    // =====================================================================
5327
5328    #[test]
5329    fn gl_shader_new_reports_no_shader_compiler_instead_of_panicking() {
5330        // A driver that reports no shader compiler (GL_SHADER_COMPILER == FALSE)
5331        // must produce a clean Err -- for ANY source, including empty, garbage,
5332        // unicode and very large ones.
5333        let ctx = null_ctx();
5334        let huge = "x".repeat(200_000);
5335        for (vert, frag) in [
5336            ("", ""),
5337            ("   \t\n  ", "\n\n"),
5338            ("not glsl at all ;;;{{{", "\u{1F600}"),
5339            ("void main(){}", "void main(){}"),
5340            (huge.as_str(), huge.as_str()),
5341        ] {
5342            let err = GlShader::new(&ctx, vert, frag).unwrap_err();
5343            assert_eq!(err, GlShaderCreateError::NoShaderCompiler);
5344        }
5345    }
5346
5347    #[test]
5348    fn shader_error_types_format_without_panicking_on_unicode_and_extremes() {
5349        let vert = VertexShaderCompileError {
5350            error_id: i32::MIN,
5351            info_log: AzString::from("\u{1F600} log".to_string()),
5352        };
5353        let frag = FragmentShaderCompileError {
5354            error_id: i32::MAX,
5355            info_log: AzString::from(String::new()),
5356        };
5357        let link = GlShaderLinkError {
5358            error_id: -1,
5359            info_log: AzString::from("multi\nline\0log".to_string()),
5360        };
5361
5362        assert!(alloc::format!("{vert}").contains("-2147483648"));
5363        assert!(alloc::format!("{vert}").contains("\u{1F600}"));
5364        assert!(alloc::format!("{frag}").contains("2147483647"));
5365
5366        let compile = GlShaderCompileError::Vertex(vert);
5367        assert!(alloc::format!("{compile}").contains("Failed to compile vertex shader"));
5368        // Debug delegates to Display -- must agree, and must not recurse.
5369        assert_eq!(alloc::format!("{compile:?}"), alloc::format!("{compile}"));
5370
5371        let frag_err = GlShaderCompileError::Fragment(frag);
5372        assert!(alloc::format!("{frag_err}").contains("Failed to compile fragment shader"));
5373
5374        let create = GlShaderCreateError::Link(link);
5375        assert!(alloc::format!("{create}").contains("Shader linking error"));
5376        assert_eq!(alloc::format!("{create:?}"), alloc::format!("{create}"));
5377        assert!(alloc::format!("{}", GlShaderCreateError::NoShaderCompiler)
5378            .contains("doesn't include a shader compiler"));
5379    }
5380
5381    // =====================================================================
5382    // Texture -- getters, refcounting, and the degenerate-driver paths
5383    // =====================================================================
5384
5385    #[test]
5386    fn texture_descriptor_mirrors_the_texture_including_extreme_sizes() {
5387        let tex = test_texture(42, 640, 480);
5388        let d = tex.get_descriptor();
5389        assert_eq!(d.width, 640);
5390        assert_eq!(d.height, 480);
5391        assert_eq!(d.format, RawImageFormat::RGBA8);
5392        assert_eq!(d.offset, 0);
5393        assert!(!d.flags.is_opaque);
5394        assert!(!d.flags.allow_mipmaps, "textures map 1:1, never mipmapped");
5395
5396        // Zero-size and u32::MAX-size must not panic or wrap in the descriptor.
5397        let zero = test_texture(1, 0, 0);
5398        assert_eq!(zero.get_descriptor().width, 0);
5399        assert_eq!(zero.get_descriptor().height, 0);
5400
5401        let huge = test_texture(1, u32::MAX, u32::MAX);
5402        assert_eq!(huge.get_descriptor().width, u32::MAX as usize);
5403        assert_eq!(huge.get_descriptor().height, u32::MAX as usize);
5404
5405        // is_opaque must be carried through from the flags, not hardcoded.
5406        let opaque = Texture::create(
5407            7,
5408            TextureFlags {
5409                is_opaque: true,
5410                is_video_texture: true,
5411            },
5412            PhysicalSizeU32 {
5413                width: 2,
5414                height: 2,
5415            },
5416            ColorU {
5417                r: 0,
5418                g: 0,
5419                b: 0,
5420                a: 0,
5421            },
5422            null_ctx(),
5423            RawImageFormat::BGRA8,
5424        );
5425        assert!(opaque.get_descriptor().flags.is_opaque);
5426        assert_eq!(opaque.get_descriptor().format, RawImageFormat::BGRA8);
5427    }
5428
5429    #[test]
5430    fn texture_clone_and_drop_share_one_refcount_without_double_freeing() {
5431        // Texture is refcounted through a raw Box<AtomicUsize>; a miscount here
5432        // is a double-free (or a leaked GL texture). Exercise fan-out + drop.
5433        let tex = test_texture(9, 4, 4);
5434        let clones: Vec<Texture> = (0..16).map(|_| tex.clone()).collect();
5435        for c in &clones {
5436            assert_eq!(c.texture_id, 9);
5437            assert_eq!(c.size, tex.size);
5438            assert_eq!(c.format, tex.format);
5439            assert!(c.run_destructor);
5440        }
5441        // Equality/Hash are by texture id.
5442        assert_eq!(clones[0], tex);
5443        assert_eq!(clones[0], clones[1]);
5444        assert_ne!(tex, test_texture(10, 4, 4));
5445
5446        drop(clones); // 16 drops...
5447        drop(tex); // ...then the last one actually frees.
5448    }
5449
5450    #[test]
5451    fn texture_display_and_debug_render_id_and_size() {
5452        let tex = test_texture(3, 16, 32);
5453        assert_eq!(alloc::format!("{tex}"), "Texture { id: 3, 16x32 }");
5454        // Debug delegates to Display.
5455        assert_eq!(alloc::format!("{tex:?}"), alloc::format!("{tex}"));
5456    }
5457
5458    #[test]
5459    fn texture_paint_stroke_is_a_noop_when_gl_is_unusable() {
5460        // Documented: "No-op if the GL context is unusable". With no brush shader
5461        // (program id 0) this must bail out before touching GL -- including for
5462        // NaN / infinite / negative radii and coordinates, which must not produce
5463        // a huge dab loop. (`!(radius > 0.0)` is written that way precisely so it
5464        // also rejects NaN.)
5465        let mut tex = test_texture(5, 8, 8);
5466        assert_eq!(tex.gl_context.get_brush_shader(), 0);
5467
5468        for radius in [1.0, 0.0, -1.0, f32::NAN, f32::INFINITY, f32::MAX] {
5469            let mut brush = Brush::new(
5470                ColorU {
5471                    r: 255,
5472                    g: 0,
5473                    b: 0,
5474                    a: 255,
5475                },
5476                radius,
5477            );
5478            brush.hardness = f32::NAN;
5479            brush.flow = f32::INFINITY;
5480            brush.spacing = 0.0; // would be a divide-by-zero step without the .max(0.01)
5481            tex.paint_stroke(f32::NAN, f32::NEG_INFINITY, f32::MAX, -0.0, brush);
5482            tex.paint_dot(f32::NAN, f32::NAN, brush);
5483        }
5484
5485        // A zero-sized texture is the other early-out (tw/th <= 0).
5486        let mut zero = test_texture(6, 0, 0);
5487        zero.paint_dot(
5488            0.0,
5489            0.0,
5490            Brush::new(
5491                ColorU {
5492                    r: 1,
5493                    g: 1,
5494                    b: 1,
5495                    a: 1,
5496                },
5497                4.0,
5498            ),
5499        );
5500    }
5501
5502    #[test]
5503    fn texture_copy_to_raw_image_returns_a_null_image_on_every_degenerate_input() {
5504        // The `w <= 0 || h <= 0` guard is load-bearing: size is a u32 but is cast
5505        // to i32 for glReadPixels, so a width >= 2^31 goes NEGATIVE. Without the
5506        // guard that reaches gl-context-loader's
5507        //   `vec![0; width as usize * height as usize * bit_depth]`
5508        // which sign-extends the negative width to ~usize::MAX -> overflow panic
5509        // (debug) / exabyte allocation (release). Prove the guard holds.
5510        let is_null_image = |img: &RawImage| img.width == 0 && img.height == 0;
5511
5512        // texture id 0 -> null image
5513        assert!(is_null_image(&test_texture(0, 16, 16).copy_to_raw_image()));
5514        // zero size -> null image
5515        assert!(is_null_image(&test_texture(1, 0, 0).copy_to_raw_image()));
5516        assert!(is_null_image(&test_texture(1, 16, 0).copy_to_raw_image()));
5517        assert!(is_null_image(&test_texture(1, 0, 16).copy_to_raw_image()));
5518
5519        // u32::MAX / 2^31 both cast to a NEGATIVE i32 and MUST be caught by the guard.
5520        assert!(is_null_image(
5521            &test_texture(1, u32::MAX, u32::MAX).copy_to_raw_image()
5522        ));
5523        assert!(is_null_image(
5524            &test_texture(1, 1 << 31, 1 << 31).copy_to_raw_image()
5525        ));
5526
5527        // Valid id + valid size, but the driver hands back no framebuffer
5528        // (`fbo == 0`) -> still a clean null image, no unwrap panic.
5529        assert!(is_null_image(&test_texture(1, 4, 4).copy_to_raw_image()));
5530    }
5531
5532    #[test]
5533    #[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
5534    fn texture_clear_panics_when_the_driver_allocates_no_framebuffer() {
5535        // DOCUMENTED panic: "Panics if no framebuffer/depthbuffer was allocated
5536        // (the GL object lists are empty)." A driver that generates nothing makes
5537        // gen_framebuffers(1) return an EMPTY list, so `.get(0).unwrap()` panics.
5538        // Pinned as documented behavior -- note that the sibling paths
5539        // (paint_stroke / copy_to_raw_image) degrade gracefully instead.
5540        test_texture(1, 4, 4).clear();
5541    }
5542
5543    // =====================================================================
5544    // VertexArrayObject / VertexBuffer
5545    // =====================================================================
5546
5547    #[test]
5548    fn vertex_array_object_clone_and_drop_share_one_refcount() {
5549        let layout = VertexLayout {
5550            fields: VertexAttributeVec::from_const_slice(&[]),
5551        };
5552        let vao = VertexArrayObject::new(layout, 77, null_ctx());
5553        assert_eq!(vao.vao_id, 77);
5554        assert!(vao.run_destructor);
5555
5556        let clones: Vec<VertexArrayObject> = (0..8).map(|_| vao.clone()).collect();
5557        assert!(clones.iter().all(|c| c.vao_id == 77));
5558        assert_eq!(clones[0], vao);
5559        drop(clones);
5560        drop(vao);
5561    }
5562
5563    #[test]
5564    fn vertex_buffer_new_raw_keeps_its_fields_and_refcounts_its_clones() {
5565        let vao = VertexArrayObject::new(
5566            VertexLayout {
5567                fields: VertexAttributeVec::from_const_slice(&[]),
5568            },
5569            1,
5570            null_ctx(),
5571        );
5572        let vb = VertexBuffer::new_raw(11, 300, vao, 22, 40, IndexBufferFormat::TriangleStrip);
5573
5574        assert_eq!(vb.vertex_buffer_id, 11);
5575        assert_eq!(vb.vertex_buffer_len, 300);
5576        assert_eq!(vb.index_buffer_id, 22);
5577        assert_eq!(vb.index_buffer_len, 40);
5578        assert_eq!(vb.index_buffer_format, IndexBufferFormat::TriangleStrip);
5579        assert_eq!(
5580            alloc::format!("{vb}"),
5581            "VertexBuffer { buffer: 11 (length: 300) }"
5582        );
5583
5584        // Eq/Hash are by vertex_buffer_id.
5585        let clones: Vec<VertexBuffer> = (0..8).map(|_| vb.clone()).collect();
5586        assert_eq!(clones[0], vb);
5587        drop(clones);
5588        drop(vb);
5589
5590        // A zero-length buffer is degenerate but legal.
5591        let empty_vao = VertexArrayObject::new(
5592            VertexLayout {
5593                fields: VertexAttributeVec::from_const_slice(&[]),
5594            },
5595            0,
5596            null_ctx(),
5597        );
5598        let empty = VertexBuffer::new_raw(0, 0, empty_vao, 0, 0, IndexBufferFormat::Points);
5599        assert_eq!(empty.vertex_buffer_len, 0);
5600    }
5601
5602    #[allow(dead_code)] // the field only exists to give the vertex a realistic size/layout
5603    struct TestVertex {
5604        _xy: [f32; 2],
5605    }
5606
5607    impl VertexLayoutDescription for TestVertex {
5608        fn get_description() -> VertexLayout {
5609            VertexLayout {
5610                fields: vec![VertexAttribute {
5611                    va_name: AzString::from_const_str("vAttrXY"),
5612                    layout_location: OptionUsize::None,
5613                    attribute_type: VertexAttributeType::Float,
5614                    item_count: 2,
5615                }]
5616                .into(),
5617            }
5618        }
5619    }
5620
5621    #[test]
5622    #[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
5623    fn vertex_buffer_new_panics_when_the_driver_allocates_no_vao() {
5624        // DOCUMENTED panic: "Panics if the GL driver failed to create the
5625        // vertex-array/buffer objects (the returned id lists are empty)."
5626        let verts = [TestVertex { _xy: [0.0, 0.0] }];
5627        let _ = VertexBuffer::new(
5628            null_ctx(),
5629            0,
5630            &verts[..],
5631            &[0u32],
5632            IndexBufferFormat::Triangles,
5633        );
5634    }
5635
5636    // =====================================================================
5637    // The process-global GL texture table.
5638    //
5639    // ACTIVE_GL_TEXTURES is a `static mut` with no lock (GL is single-threaded
5640    // by design), and cargo test runs #[test]s on parallel threads. So ALL of
5641    // its coverage lives in this ONE test -- splitting it up would race the
5642    // table against itself. No other test in this crate touches it.
5643    // =====================================================================
5644
5645    #[test]
5646    fn gl_texture_cache_insert_lookup_and_eviction_lifecycle() {
5647        let doc = DocumentId {
5648            namespace_id: crate::resources::IdNamespace(7),
5649            id: 1,
5650        };
5651        let other_doc = DocumentId {
5652            namespace_id: crate::resources::IdNamespace(7),
5653            id: 2,
5654        };
5655
5656        // --- Empty / uninitialized table: every accessor must degrade, not panic.
5657        gl_textures_clear_opengl_cache();
5658        let stale = ExternalImageId { inner: u64::MAX };
5659        assert!(get_opengl_texture(&stale).is_none());
5660        assert!(
5661            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(0), &stale).is_none()
5662        );
5663        gl_textures_remove_epochs_from_pipeline(&doc, Epoch::from(0));
5664        gl_textures_remove_active_pipeline(&doc);
5665        gl_textures_clear_opengl_cache(); // idempotent
5666
5667        // --- Insert into an UNINITIALIZED table.
5668        // NOTE: the doc comment on insert_into_active_gl_textures claims it
5669        // "Panics if the global active-GL-texture table has not been
5670        // initialized" -- it does not; it initializes the table itself. The doc
5671        // is stale (see the report). Pin the real behavior.
5672        let id5 = insert_into_active_gl_textures(doc, Epoch::from(5), test_texture(11, 4, 8));
5673        let id7 = insert_into_active_gl_textures(doc, Epoch::from(7), test_texture(22, 16, 32));
5674        assert_ne!(id5, id7, "each insert must mint a unique ExternalImageId");
5675
5676        // --- Lookup: id -> (texture id, (w, h) as f32).
5677        assert_eq!(get_opengl_texture(&id5), Some((11, (4.0, 8.0))));
5678        assert_eq!(get_opengl_texture(&id7), Some((22, (16.0, 32.0))));
5679        assert!(get_opengl_texture(&stale).is_none());
5680
5681        // A u32::MAX-sized texture goes through a lossy u32 -> f32 cast:
5682        // u32::MAX (2^32 - 1) has no exact f32, so it rounds UP to 2^32.
5683        let id_huge =
5684            insert_into_active_gl_textures(doc, Epoch::from(5), test_texture(33, u32::MAX, 1));
5685        assert_eq!(get_opengl_texture(&id_huge), Some((33, (4_294_967_296.0, 1.0))));
5686
5687        // --- Epoch eviction is STRICTLY older-than: epoch 5 goes, epoch 7 stays.
5688        gl_textures_remove_epochs_from_pipeline(&doc, Epoch::from(7));
5689        assert!(get_opengl_texture(&id5).is_none(), "epoch 5 < 7 must be evicted");
5690        assert!(get_opengl_texture(&id_huge).is_none(), "epoch 5 < 7 must be evicted");
5691        assert_eq!(
5692            get_opengl_texture(&id7),
5693            Some((22, (16.0, 32.0))),
5694            "epoch 7 is NOT < 7, so it must survive"
5695        );
5696
5697        // Evicting for an unknown document must be a no-op, not a panic.
5698        gl_textures_remove_epochs_from_pipeline(&other_doc, Epoch::from(u32::MAX));
5699        assert_eq!(get_opengl_texture(&id7), Some((22, (16.0, 32.0))));
5700
5701        // --- remove_single: Some(()) when the (doc, epoch) path exists...
5702        assert_eq!(
5703            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(7), &id7),
5704            Some(())
5705        );
5706        assert!(get_opengl_texture(&id7).is_none());
5707        // ...including a second removal of an already-gone image (the path is
5708        // still there, so it reports Some(()) rather than None)...
5709        assert_eq!(
5710            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(7), &id7),
5711            Some(())
5712        );
5713        // ...but None when the document or the epoch is unknown.
5714        assert!(
5715            remove_single_texture_from_active_gl_textures(&other_doc, &Epoch::from(7), &id7)
5716                .is_none()
5717        );
5718        assert!(remove_single_texture_from_active_gl_textures(
5719            &doc,
5720            &Epoch::from(u32::MAX),
5721            &id7
5722        )
5723        .is_none());
5724
5725        // --- remove_active_pipeline drops the whole document.
5726        let id_a = insert_into_active_gl_textures(doc, Epoch::from(1), test_texture(44, 2, 2));
5727        let id_b = insert_into_active_gl_textures(other_doc, Epoch::from(1), test_texture(55, 2, 2));
5728        assert!(get_opengl_texture(&id_a).is_some());
5729        gl_textures_remove_active_pipeline(&doc);
5730        assert!(get_opengl_texture(&id_a).is_none(), "doc was removed");
5731        assert!(
5732            get_opengl_texture(&id_b).is_some(),
5733            "other_doc must be untouched"
5734        );
5735
5736        // --- clear drops everything.
5737        gl_textures_clear_opengl_cache();
5738        assert!(get_opengl_texture(&id_b).is_none());
5739
5740        // Leave the table clean for anything that runs after us.
5741        gl_textures_clear_opengl_cache();
5742    }
5743}