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]
121    pub const fn as_str(&self) -> &str {
122        // AUDIT: `from_raw_parts`/`from_utf8_unchecked` are UB on a null ptr
123        // (even with len==0). FFI callers can hand us a null/empty Refstr, so
124        // return an empty `&str` instead of forming a slice over null.
125        if self.ptr.is_null() || self.len == 0 {
126            return "";
127        }
128        unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.ptr, self.len)) }
129    }
130}
131
132impl From<&str> for Refstr {
133    fn from(s: &str) -> Self {
134        Self {
135            ptr: s.as_ptr(),
136            len: s.len(),
137        }
138    }
139}
140
141/// FFI-safe wrapper for `&[&str]`.
142#[repr(C)]
143pub struct RefstrVecRef {
144    pub ptr: *const Refstr,
145    pub len: usize,
146}
147
148impl Clone for RefstrVecRef {
149    fn clone(&self) -> Self {
150        Self {
151            ptr: self.ptr,
152            len: self.len,
153        }
154    }
155}
156
157impl fmt::Debug for RefstrVecRef {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        self.as_slice().fmt(f)
160    }
161}
162
163impl RefstrVecRef {
164    #[must_use]
165    pub const fn as_slice(&self) -> &[Refstr] {
166        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
167        if self.ptr.is_null() || self.len == 0 {
168            return &[];
169        }
170        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
171    }
172}
173
174impl From<&[Refstr]> for RefstrVecRef {
175    fn from(s: &[Refstr]) -> Self {
176        Self {
177            ptr: s.as_ptr(),
178            len: s.len(),
179        }
180    }
181}
182
183/// FFI-safe wrapper for `&mut [GLint64]`.
184#[repr(C)]
185pub struct GLint64VecRefMut {
186    pub ptr: *mut i64,
187    pub len: usize,
188}
189
190impl Clone for GLint64VecRefMut {
191    fn clone(&self) -> Self {
192        Self {
193            ptr: self.ptr,
194            len: self.len,
195        }
196    }
197}
198
199impl fmt::Debug for GLint64VecRefMut {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        self.as_slice().fmt(f)
202    }
203}
204
205impl From<&mut [GLint64]> for GLint64VecRefMut {
206    fn from(s: &mut [GLint64]) -> Self {
207        Self {
208            ptr: s.as_mut_ptr(),
209            len: s.len(),
210        }
211    }
212}
213
214impl GLint64VecRefMut {
215    #[must_use]
216    pub const fn as_slice(&self) -> &[GLint64] {
217        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
218        if self.ptr.is_null() || self.len == 0 {
219            return &[];
220        }
221        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
222    }
223    const fn as_mut_slice(&mut self) -> &mut [GLint64] {
224        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
225        if self.ptr.is_null() || self.len == 0 {
226            return &mut [];
227        }
228        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
229    }
230}
231
232/// FFI-safe wrapper for `&mut [GLfloat]`.
233#[repr(C)]
234pub struct GLfloatVecRefMut {
235    pub ptr: *mut f32,
236    pub len: usize,
237}
238
239impl Clone for GLfloatVecRefMut {
240    fn clone(&self) -> Self {
241        Self {
242            ptr: self.ptr,
243            len: self.len,
244        }
245    }
246}
247
248impl fmt::Debug for GLfloatVecRefMut {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        self.as_slice().fmt(f)
251    }
252}
253
254impl From<&mut [GLfloat]> for GLfloatVecRefMut {
255    fn from(s: &mut [GLfloat]) -> Self {
256        Self {
257            ptr: s.as_mut_ptr(),
258            len: s.len(),
259        }
260    }
261}
262
263impl GLfloatVecRefMut {
264    #[must_use]
265    pub const fn as_slice(&self) -> &[GLfloat] {
266        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
267        if self.ptr.is_null() || self.len == 0 {
268            return &[];
269        }
270        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
271    }
272    const fn as_mut_slice(&mut self) -> &mut [GLfloat] {
273        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
274        if self.ptr.is_null() || self.len == 0 {
275            return &mut [];
276        }
277        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
278    }
279}
280
281/// FFI-safe wrapper for `&mut [GLint]`.
282#[repr(C)]
283pub struct GLintVecRefMut {
284    pub ptr: *mut i32,
285    pub len: usize,
286}
287
288impl Clone for GLintVecRefMut {
289    fn clone(&self) -> Self {
290        Self {
291            ptr: self.ptr,
292            len: self.len,
293        }
294    }
295}
296
297impl fmt::Debug for GLintVecRefMut {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        self.as_slice().fmt(f)
300    }
301}
302
303impl From<&mut [GLint]> for GLintVecRefMut {
304    fn from(s: &mut [GLint]) -> Self {
305        Self {
306            ptr: s.as_mut_ptr(),
307            len: s.len(),
308        }
309    }
310}
311
312impl GLintVecRefMut {
313    #[must_use]
314    pub const fn as_slice(&self) -> &[GLint] {
315        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
316        if self.ptr.is_null() || self.len == 0 {
317            return &[];
318        }
319        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
320    }
321    const fn as_mut_slice(&mut self) -> &mut [GLint] {
322        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
323        if self.ptr.is_null() || self.len == 0 {
324            return &mut [];
325        }
326        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
327    }
328}
329
330/// FFI-safe wrapper for `&[GLuint]`.
331#[repr(C)]
332pub struct GLuintVecRef {
333    pub ptr: *const u32,
334    pub len: usize,
335}
336
337impl Clone for GLuintVecRef {
338    fn clone(&self) -> Self {
339        Self {
340            ptr: self.ptr,
341            len: self.len,
342        }
343    }
344}
345
346impl fmt::Debug for GLuintVecRef {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        self.as_slice().fmt(f)
349    }
350}
351
352impl From<&[GLuint]> for GLuintVecRef {
353    fn from(s: &[GLuint]) -> Self {
354        Self {
355            ptr: s.as_ptr(),
356            len: s.len(),
357        }
358    }
359}
360
361impl GLuintVecRef {
362    #[must_use]
363    pub const fn as_slice(&self) -> &[GLuint] {
364        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
365        if self.ptr.is_null() || self.len == 0 {
366            return &[];
367        }
368        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
369    }
370}
371
372/// FFI-safe wrapper for `&[GLenum]`.
373#[repr(C)]
374pub struct GLenumVecRef {
375    pub ptr: *const u32,
376    pub len: usize,
377}
378
379impl Clone for GLenumVecRef {
380    fn clone(&self) -> Self {
381        Self {
382            ptr: self.ptr,
383            len: self.len,
384        }
385    }
386}
387
388impl fmt::Debug for GLenumVecRef {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        self.as_slice().fmt(f)
391    }
392}
393
394impl From<&[GLenum]> for GLenumVecRef {
395    fn from(s: &[GLenum]) -> Self {
396        Self {
397            ptr: s.as_ptr(),
398            len: s.len(),
399        }
400    }
401}
402
403impl GLenumVecRef {
404    #[must_use]
405    pub const fn as_slice(&self) -> &[GLenum] {
406        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
407        if self.ptr.is_null() || self.len == 0 {
408            return &[];
409        }
410        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
411    }
412}
413
414/// FFI-safe wrapper for `&[u8]`.
415#[repr(C)]
416pub struct U8VecRef {
417    pub ptr: *const u8,
418    pub len: usize,
419}
420
421impl Clone for U8VecRef {
422    fn clone(&self) -> Self {
423        Self {
424            ptr: self.ptr,
425            len: self.len,
426        }
427    }
428}
429
430impl From<&[u8]> for U8VecRef {
431    fn from(s: &[u8]) -> Self {
432        Self {
433            ptr: s.as_ptr(),
434            len: s.len(),
435        }
436    }
437}
438
439impl U8VecRef {
440    #[must_use]
441    pub const fn as_slice(&self) -> &[u8] {
442        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
443        if self.ptr.is_null() || self.len == 0 {
444            return &[];
445        }
446        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
447    }
448}
449
450impl fmt::Debug for U8VecRef {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        self.as_slice().fmt(f)
453    }
454}
455
456impl PartialOrd for U8VecRef {
457    fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
458        self.as_slice().partial_cmp(rhs.as_slice())
459    }
460}
461
462impl Ord for U8VecRef {
463    fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
464        self.as_slice().cmp(rhs.as_slice())
465    }
466}
467
468impl PartialEq for U8VecRef {
469    fn eq(&self, rhs: &Self) -> bool {
470        self.as_slice().eq(rhs.as_slice())
471    }
472}
473
474impl Eq for U8VecRef {}
475
476impl Hash for U8VecRef {
477    fn hash<H>(&self, state: &mut H)
478    where
479        H: Hasher,
480    {
481        self.as_slice().hash(state);
482    }
483}
484
485/// FFI-safe wrapper for `&[f32]`.
486#[repr(C)]
487pub struct F32VecRef {
488    pub ptr: *const f32,
489    pub len: usize,
490}
491
492impl Clone for F32VecRef {
493    fn clone(&self) -> Self {
494        Self {
495            ptr: self.ptr,
496            len: self.len,
497        }
498    }
499}
500
501impl fmt::Debug for F32VecRef {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        self.as_slice().fmt(f)
504    }
505}
506
507impl From<&[f32]> for F32VecRef {
508    fn from(s: &[f32]) -> Self {
509        Self {
510            ptr: s.as_ptr(),
511            len: s.len(),
512        }
513    }
514}
515
516impl F32VecRef {
517    #[must_use]
518    pub const fn as_slice(&self) -> &[f32] {
519        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
520        if self.ptr.is_null() || self.len == 0 {
521            return &[];
522        }
523        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
524    }
525}
526
527/// FFI-safe wrapper for `&[i32]`.
528#[repr(C)]
529pub struct I32VecRef {
530    pub ptr: *const i32,
531    pub len: usize,
532}
533
534impl Clone for I32VecRef {
535    fn clone(&self) -> Self {
536        Self {
537            ptr: self.ptr,
538            len: self.len,
539        }
540    }
541}
542
543impl fmt::Debug for I32VecRef {
544    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545        self.as_slice().fmt(f)
546    }
547}
548
549impl From<&[i32]> for I32VecRef {
550    fn from(s: &[i32]) -> Self {
551        Self {
552            ptr: s.as_ptr(),
553            len: s.len(),
554        }
555    }
556}
557
558impl I32VecRef {
559    #[must_use]
560    pub const fn as_slice(&self) -> &[i32] {
561        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
562        if self.ptr.is_null() || self.len == 0 {
563            return &[];
564        }
565        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
566    }
567}
568
569/// FFI-safe wrapper for `&mut [GLboolean]` (i.e. `&mut [u8]`).
570#[repr(C)]
571pub struct GLbooleanVecRefMut {
572    pub ptr: *mut u8,
573    pub len: usize,
574}
575
576impl Clone for GLbooleanVecRefMut {
577    fn clone(&self) -> Self {
578        Self {
579            ptr: self.ptr,
580            len: self.len,
581        }
582    }
583}
584
585impl fmt::Debug for GLbooleanVecRefMut {
586    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587        self.as_slice().fmt(f)
588    }
589}
590
591impl From<&mut [GLboolean]> for GLbooleanVecRefMut {
592    fn from(s: &mut [GLboolean]) -> Self {
593        Self {
594            ptr: s.as_mut_ptr(),
595            len: s.len(),
596        }
597    }
598}
599
600impl GLbooleanVecRefMut {
601    #[must_use]
602    pub const fn as_slice(&self) -> &[GLboolean] {
603        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
604        if self.ptr.is_null() || self.len == 0 {
605            return &[];
606        }
607        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
608    }
609    const fn as_mut_slice(&mut self) -> &mut [GLboolean] {
610        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
611        if self.ptr.is_null() || self.len == 0 {
612            return &mut [];
613        }
614        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
615    }
616}
617
618/// FFI-safe wrapper for `&mut [u8]`.
619#[repr(C)]
620pub struct U8VecRefMut {
621    pub ptr: *mut u8,
622    pub len: usize,
623}
624
625impl Clone for U8VecRefMut {
626    fn clone(&self) -> Self {
627        Self {
628            ptr: self.ptr,
629            len: self.len,
630        }
631    }
632}
633
634impl fmt::Debug for U8VecRefMut {
635    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
636        self.as_slice().fmt(f)
637    }
638}
639
640impl From<&mut [u8]> for U8VecRefMut {
641    fn from(s: &mut [u8]) -> Self {
642        Self {
643            ptr: s.as_mut_ptr(),
644            len: s.len(),
645        }
646    }
647}
648
649impl U8VecRefMut {
650    #[must_use]
651    pub const fn as_slice(&self) -> &[u8] {
652        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
653        if self.ptr.is_null() || self.len == 0 {
654            return &[];
655        }
656        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
657    }
658    const fn as_mut_slice(&mut self) -> &mut [u8] {
659        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
660        if self.ptr.is_null() || self.len == 0 {
661            return &mut [];
662        }
663        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
664    }
665}
666
667impl_option!(
668    U8VecRef,
669    OptionU8VecRef,
670    copy = false,
671    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
672);
673
674#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
675#[repr(C)]
676pub struct DebugMessage {
677    pub message: AzString,
678    pub source: GLenum,
679    pub ty: GLenum,
680    pub id: GLenum,
681    pub severity: GLenum,
682}
683
684impl_option!(
685    DebugMessage,
686    OptionDebugMessage,
687    copy = false,
688    [Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
689);
690
691impl_vec!(
692    DebugMessage,
693    DebugMessageVec,
694    DebugMessageVecDestructor,
695    DebugMessageVecDestructorType,
696    DebugMessageVecSlice,
697    OptionDebugMessage
698);
699impl_vec_debug!(DebugMessage, DebugMessageVec);
700impl_vec_partialord!(DebugMessage, DebugMessageVec);
701impl_vec_ord!(DebugMessage, DebugMessageVec);
702impl_vec_clone!(DebugMessage, DebugMessageVec, DebugMessageVecDestructor);
703impl_vec_partialeq!(DebugMessage, DebugMessageVec);
704impl_vec_eq!(DebugMessage, DebugMessageVec);
705impl_vec_hash!(DebugMessage, DebugMessageVec);
706
707impl_vec!(
708    GLint,
709    GLintVec,
710    GLintVecDestructor,
711    GLintVecDestructorType,
712    GLintVecSlice,
713    OptionI32
714);
715impl_vec_debug!(GLint, GLintVec);
716impl_vec_partialord!(GLint, GLintVec);
717impl_vec_ord!(GLint, GLintVec);
718impl_vec_clone!(GLint, GLintVec, GLintVecDestructor);
719impl_vec_partialeq!(GLint, GLintVec);
720impl_vec_eq!(GLint, GLintVec);
721impl_vec_hash!(GLint, GLintVec);
722
723impl_vec!(
724    GLuint,
725    GLuintVec,
726    GLuintVecDestructor,
727    GLuintVecDestructorType,
728    GLuintVecSlice,
729    OptionU32
730);
731impl_vec_debug!(GLuint, GLuintVec);
732impl_vec_partialord!(GLuint, GLuintVec);
733impl_vec_ord!(GLuint, GLuintVec);
734impl_vec_clone!(GLuint, GLuintVec, GLuintVecDestructor);
735impl_vec_partialeq!(GLuint, GLuintVec);
736impl_vec_eq!(GLuint, GLuintVec);
737impl_vec_hash!(GLuint, GLuintVec);
738
739#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
740#[repr(C)]
741pub enum GlType {
742    Gl,
743    Gles,
744}
745
746impl From<GlContextGlType> for GlType {
747    fn from(a: GlContextGlType) -> Self {
748        match a {
749            GlContextGlType::Gl => Self::Gl,
750            GlContextGlType::GlEs => Self::Gles,
751        }
752    }
753}
754
755// (U8Vec, u32)
756#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
757#[repr(C)]
758// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
759#[allow(clippy::pub_underscore_fields)]
760pub struct GetProgramBinaryReturn {
761    pub _0: U8Vec,
762    pub _1: u32,
763}
764
765// (i32, u32, AzString)
766#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
767#[repr(C)]
768// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
769#[allow(clippy::pub_underscore_fields)]
770pub struct GetActiveAttribReturn {
771    pub _0: i32,
772    pub _1: u32,
773    pub _2: AzString,
774}
775
776// (i32, u32, AzString)
777#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
778#[repr(C)]
779// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
780#[allow(clippy::pub_underscore_fields)]
781pub struct GetActiveUniformReturn {
782    pub _0: i32,
783    pub _1: u32,
784    pub _2: AzString,
785}
786
787#[repr(C)]
788pub struct GLsyncPtr {
789    pub ptr: *const c_void, /* *const __GLsync */
790    pub run_destructor: bool,
791}
792
793impl Clone for GLsyncPtr {
794    fn clone(&self) -> Self {
795        Self {
796            ptr: self.ptr,
797            run_destructor: true,
798        }
799    }
800}
801
802impl fmt::Debug for GLsyncPtr {
803    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
804        write!(f, "0x{:0x}", self.ptr as usize)
805    }
806}
807
808impl GLsyncPtr {
809    #[must_use]
810    pub const fn new(p: GLsync) -> Self {
811        Self {
812            ptr: p,
813            run_destructor: true,
814        }
815    }
816    #[must_use]
817    pub fn get(self) -> GLsync {
818        self.ptr as GLsync
819    }
820}
821
822impl Drop for GLsyncPtr {
823    fn drop(&mut self) {
824        self.run_destructor = false;
825    }
826}
827
828/// Each pipeline (window) has its own OpenGL textures. GL Textures can technically
829/// be shared across pipelines, however this turns out to be very difficult in practice.
830pub type GlTextureStorage = OrderedMap<Epoch, OrderedMap<ExternalImageId, Texture>>;
831
832/// Non-cleaned up textures. When a `GlTexture` is registered, it has to stay active as long
833/// as `WebRender` needs it for drawing. To transparently do this, we store the epoch that the
834/// texture was originally created with, and check, **after we have drawn the frame**,
835/// if there are any textures that need cleanup.
836///
837/// Because the Texture2d is wrapped in an Rc, the destructor (which cleans up the OpenGL
838/// texture) does not run until we remove the textures
839///
840/// Note: Because textures could be used after the current draw call (ex. for scrolling),
841/// the `ACTIVE_GL_TEXTURES` are indexed by their epoch. Use `renderer.flush_pipeline_info()`
842/// to see which textures are still active and which ones can be safely removed.
843///
844/// See: <https://github.com/servo/webrender/issues/2940>
845///
846/// WARNING: Not thread-safe (however, the Texture itself is thread-unsafe, so it's unlikely to ever
847/// be misused)
848static mut ACTIVE_GL_TEXTURES: Option<OrderedMap<DocumentId, GlTextureStorage>> = None;
849
850/// Sound accessor for the process-global GL texture table.
851///
852/// AUDIT: GL access is single-threaded by design (see the WARNING above — the
853/// `Texture` itself is thread-unsafe), so no lock is used. The soundness fix
854/// here is to never form an *implicit* reference to the `static mut` (the
855/// edition-2024 `static_mut_refs` hard error + `&mut`-aliasing UB that
856/// `ACTIVE_GL_TEXTURES.as_mut()` / `.as_ref()` triggered). Deriving the
857/// reference from `&raw mut` gives it correct provenance without ever naming
858/// the static as an auto-ref place. Callers must not hold two of these at once
859/// (they don't — every use is a single non-reentrant scope).
860#[inline]
861#[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`)
862fn active_gl_textures() -> &'static mut Option<OrderedMap<DocumentId, GlTextureStorage>> {
863    // SAFETY: `&raw mut` avoids an intermediate `&mut ACTIVE_GL_TEXTURES`; the
864    // static is valid for the whole program. Single-threaded access (GL thread).
865    unsafe { &mut *(&raw mut ACTIVE_GL_TEXTURES) }
866}
867
868/// Inserts a new texture into the OpenGL texture cache, returns a new image ID
869/// for the inserted texture
870///
871/// This function exists so azul doesn't have to use `lazy_static` as a dependency
872///
873/// # Panics
874///
875/// Panics if the global active-GL-texture table has not been initialized.
876#[must_use]
877pub fn insert_into_active_gl_textures(
878    document_id: DocumentId,
879    epoch: Epoch,
880    texture: Texture,
881) -> ExternalImageId {
882    let external_image_id = ExternalImageId::new();
883
884    let active = active_gl_textures();
885    if active.is_none() {
886        *active = Some(OrderedMap::new());
887    }
888    let active_textures = active.as_mut().unwrap();
889    let active_epochs = active_textures.entry(document_id).or_default();
890    let active_textures_for_epoch = active_epochs.entry(epoch).or_default();
891    active_textures_for_epoch.insert(external_image_id, texture);
892
893    external_image_id
894}
895
896/// Destroys all textures from the given `document_id`
897/// where the texture is **older** than the given `epoch`.
898pub fn gl_textures_remove_epochs_from_pipeline(document_id: &DocumentId, epoch: Epoch) {
899    // TODO: Handle overflow of Epochs correctly (low priority)
900    let Some(active_textures) = active_gl_textures().as_mut() else {
901        return;
902    };
903
904    let Some(active_epochs) = active_textures.get_mut(document_id) else {
905        return;
906    };
907
908    // NOTE: original code used retain() but that
909    // doesn't work on no_std
910    let mut epochs_to_remove = Vec::new();
911
912    for (gl_texture_epoch, _) in active_epochs.iter() {
913        if *gl_texture_epoch < epoch {
914            epochs_to_remove.push(*gl_texture_epoch);
915        }
916    }
917
918    for epoch in epochs_to_remove {
919        active_epochs.remove(&epoch);
920    }
921}
922
923// document_id, epoch, external_image_id
924#[must_use]
925pub fn remove_single_texture_from_active_gl_textures(
926    document_id: &DocumentId,
927    epoch: &Epoch,
928    external_image_id: &ExternalImageId,
929) -> Option<()> {
930    let active_textures = active_gl_textures().as_mut()?;
931    let epochs = active_textures.get_mut(document_id)?;
932    let images_in_epoch = epochs.get_mut(epoch)?;
933    images_in_epoch.remove(external_image_id);
934    Some(())
935}
936
937/// Removes a `DocumentId` from the active epochs
938pub fn gl_textures_remove_active_pipeline(document_id: &DocumentId) {
939    let Some(active_textures) = active_gl_textures().as_mut() else {
940        return;
941    };
942    active_textures.remove(document_id);
943}
944
945/// Destroys all textures, usually done before destroying the OpenGL context
946#[allow(clippy::cast_precision_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
947pub fn gl_textures_clear_opengl_cache() {
948    *active_gl_textures() = None;
949}
950
951// Search all epoch hash maps for the given key
952// There does not seem to be a way to get the epoch for the key,
953// so we simply have to search all active epochs
954//
955// NOTE: Invalid textures can be generated on minimize / maximize
956// Luckily, webrender simply ignores an invalid texture, so we don't
957// need to check whether a window is maximized or minimized - if
958// we encounter an invalid ID, webrender simply won't draw anything,
959// but at least it won't crash. Usually invalid textures are also 0x0
960// pixels large - so it's not like we had anything to draw anyway.
961#[allow(clippy::cast_precision_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
962#[must_use]
963pub fn get_opengl_texture(image_key: &ExternalImageId) -> Option<(GLuint, (f32, f32))> {
964    let active_textures = active_gl_textures().as_ref()?;
965    active_textures
966        .values()
967        .flat_map(|active_document| active_document.values())
968        .find_map(|active_epoch| active_epoch.get(image_key))
969        .map(|tex| {
970            (
971                tex.texture_id,
972                (tex.size.width as f32, tex.size.height as f32),
973            )
974        })
975}
976
977/// For .`get_gl_precision_format()`, but ABI-safe - returning an array or a tuple is not ABI-safe
978#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
979#[repr(C)]
980// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
981#[allow(clippy::pub_underscore_fields)]
982pub struct GlShaderPrecisionFormatReturn {
983    pub _0: GLint,
984    pub _1: GLint,
985    pub _2: GLint,
986}
987
988#[repr(C)]
989pub struct GlContextPtr {
990    /// `ManuallyDrop` so the owned `Box` is freed ONLY when `run_destructor` is
991    /// still set (see `Drop`). The codegen FFI wrappers (`AzTexture` etc.) embed
992    /// this by value AND have their own `Drop` that `drop_in_place`s the real
993    /// type first; Rust's drop glue would then drop this field a SECOND time on
994    /// the same bytes. Gating the `Box` free on `run_destructor` (which the first
995    /// drop clears in the shared memory) makes that second drop a safe no-op.
996    /// Layout is unchanged: `ManuallyDrop<Box<T>>` is a single pointer, identical
997    /// to the old `Box<T>` and to the FFI `*mut c_void`.
998    pub ptr: ManuallyDrop<Box<Rc<GlContextPtrInner>>>,
999    /// Whether to force a hardware or software renderer
1000    pub renderer_type: RendererType,
1001    pub run_destructor: bool,
1002}
1003
1004impl Clone for GlContextPtr {
1005    fn clone(&self) -> Self {
1006        Self {
1007            ptr: ManuallyDrop::new((*self.ptr).clone()),
1008            renderer_type: self.renderer_type,
1009            run_destructor: true,
1010        }
1011    }
1012}
1013
1014impl Drop for GlContextPtr {
1015    fn drop(&mut self) {
1016        // Only free the owned Box if this instance still owns it. The FFI wrapper
1017        // double-drop (see the struct doc) hits these same bytes a second time
1018        // with `run_destructor` already cleared by the first drop -> no-op, no
1019        // double-free.
1020        if self.run_destructor {
1021            self.run_destructor = false;
1022            unsafe {
1023                ManuallyDrop::drop(&mut self.ptr);
1024            }
1025        }
1026    }
1027}
1028
1029impl GlContextPtr {
1030    #[must_use]
1031    pub fn get_svg_shader(&self) -> GLuint {
1032        self.ptr.svg_shader
1033    }
1034    /// Whether this hardware GL context proved usable at construction (the SVG
1035    /// shaders compiled+linked at some GLSL version). `false` means context
1036    /// creation succeeded but the driver can't run our shaders -- the caller
1037    /// should fall back to CPU rendering. Always `false` for a Software context
1038    /// (which never compiles these shaders); only meaningful on the GPU path.
1039    #[must_use]
1040    pub fn is_gl_usable(&self) -> bool {
1041        self.ptr.svg_shader != 0
1042    }
1043    /// The GLSL `#version` the driver accepted at construction (e.g. "150" or
1044    /// "300 es"), discovered by the probe. Empty string if the context is
1045    /// unusable / software. Exposed in the API so apps can report/branch on it.
1046    #[must_use]
1047    pub fn get_usable_glsl_version(&self) -> AzString {
1048        self.ptr.glsl_version.clone()
1049    }
1050    /// Soft-brush shader program for the GPU painting API (0 if unusable).
1051    #[must_use]
1052    pub fn get_brush_shader(&self) -> GLuint {
1053        self.ptr.brush_shader
1054    }
1055    #[must_use]
1056    pub fn get_fxaa_shader(&self) -> GLuint {
1057        self.ptr.fxaa_shader
1058    }
1059}
1060
1061#[repr(C)]
1062pub struct GlContextPtrInner {
1063    pub ptr: Rc<GenericGlContext>,
1064    /// SVG shader program (library-internal use)
1065    pub svg_shader: GLuint,
1066    /// SVG multicolor shader program (library-internal use)
1067    pub svg_multicolor_shader: GLuint,
1068    /// FXAA shader program (library-internal use)
1069    pub fxaa_shader: GLuint,
1070    /// Soft-brush shader program for the GPU painting API (0 if unusable).
1071    pub brush_shader: GLuint,
1072    /// The GLSL `#version` directive that compiled (e.g. "150" or "300 es"),
1073    /// discovered by the probe in `new()`. Empty if the context is unusable.
1074    pub glsl_version: AzString,
1075}
1076
1077impl fmt::Debug for GlContextPtrInner {
1078    // `ptr` wraps the external GL context (not Debug); show the rest.
1079    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1080        f.debug_struct("GlContextPtrInner")
1081            .field("svg_shader", &self.svg_shader)
1082            .field("svg_multicolor_shader", &self.svg_multicolor_shader)
1083            .field("fxaa_shader", &self.fxaa_shader)
1084            .field("brush_shader", &self.brush_shader)
1085            .field("glsl_version", &self.glsl_version)
1086            .finish_non_exhaustive()
1087    }
1088}
1089
1090impl Drop for GlContextPtrInner {
1091    fn drop(&mut self) {
1092        self.ptr.delete_program(self.svg_shader);
1093        self.ptr.delete_program(self.svg_multicolor_shader);
1094        self.ptr.delete_program(self.fxaa_shader);
1095        if self.brush_shader != 0 {
1096            self.ptr.delete_program(self.brush_shader);
1097        }
1098    }
1099}
1100
1101impl_option!(
1102    GlContextPtr,
1103    OptionGlContextPtr,
1104    copy = false,
1105    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord]
1106);
1107
1108impl fmt::Debug for GlContextPtr {
1109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1110        write!(f, "0x{:0x}", self.as_usize())
1111    }
1112}
1113
1114static SVG_VERTEX_SHADER: &[u8] = b"#version 150
1115
1116#if __VERSION__ != 100
1117    #define varying out
1118    #define attribute in
1119#endif
1120
1121uniform vec2 vBboxSize;
1122uniform mat4 vTransformMatrix;
1123
1124attribute vec2 vAttrXY;
1125
1126void main() {
1127    vec4 vTransposed = vec4(vAttrXY, 1.0, 1.0) * vTransformMatrix;
1128    vec2 vTransposedInScreen = vTransposed.xy / vBboxSize;
1129    vec2 vCalcFinal = (vTransposedInScreen * vec2(2.0)) - vec2(1.0);
1130    gl_Position = vec4(vCalcFinal, 1.0, 1.0);
1131}";
1132
1133static SVG_FRAGMENT_SHADER: &[u8] = b"#version 150
1134
1135precision highp float;
1136
1137uniform vec4 fDrawColor;
1138
1139#if __VERSION__ == 100
1140    #define oFragColor gl_FragColor
1141#else
1142    out vec4 oFragColor;
1143#endif
1144
1145void main() {
1146    oFragColor = fDrawColor;
1147}";
1148
1149static SVG_MULTICOLOR_VERTEX_SHADER: &[u8] = b"#version 150
1150
1151#if __VERSION__ != 100
1152    #define varying out
1153    #define attribute in
1154#endif
1155
1156uniform vec2 vBboxSize;
1157uniform mat4 vTransformMatrix;
1158
1159attribute vec3 vAttrXY;
1160attribute vec4 vColor;
1161varying vec4 fColor;
1162
1163void main() {
1164    vec4 vTransposed = vec4(vAttrXY.xy, 1.0, 1.0) * vTransformMatrix;
1165    vec2 vTransposedInScreen = vTransposed.xy / vBboxSize;
1166    vec2 vCalcFinal = (vTransposedInScreen * vec2(2.0)) - vec2(1.0);
1167    gl_Position = vec4(vCalcFinal, vAttrXY.z, 1.0);
1168    fColor = vColor;
1169}";
1170
1171static SVG_MULTICOLOR_FRAGMENT_SHADER: &[u8] = b"#version 150
1172
1173precision highp float;
1174
1175#if __VERSION__ != 100
1176    #define varying in
1177#endif
1178
1179#if __VERSION__ == 100
1180    #define oFragColor gl_FragColor
1181#else
1182    out vec4 oFragColor;
1183#endif
1184
1185varying vec4 fColor;
1186
1187void main() {
1188    oFragColor = fColor;
1189}";
1190
1191// Soft-brush shaders for the GPU painting API. A unit quad [-1,1]^2 (aUv) is
1192// positioned in NDC (aPos); the fragment computes the same radial falloff as
1193// the CPU `brush_dab_coverage` (1 - smoothstep(hardness, 1, dist)) so GPU and
1194// CPU strokes match. Version-agnostic via `__VERSION__` like the SVG shaders.
1195static BRUSH_VERTEX_SHADER: &[u8] = b"#version 150
1196
1197#if __VERSION__ != 100
1198    #define varying out
1199    #define attribute in
1200#endif
1201
1202attribute vec2 aPos;
1203attribute vec2 aUv;
1204varying vec2 vUv;
1205
1206void main() {
1207    vUv = aUv;
1208    gl_Position = vec4(aPos, 0.0, 1.0);
1209}";
1210
1211static BRUSH_FRAGMENT_SHADER: &[u8] = b"#version 150
1212
1213precision highp float;
1214
1215#if __VERSION__ != 100
1216    #define varying in
1217#endif
1218
1219#if __VERSION__ == 100
1220    #define oFragColor gl_FragColor
1221#else
1222    out vec4 oFragColor;
1223#endif
1224
1225uniform vec4 uColor;     // rgb + alpha (alpha already folds in flow * color.a)
1226uniform float uHardness; // 0 = soft .. 1 = hard edge
1227
1228varying vec2 vUv;
1229
1230void main() {
1231    float d = length(vUv);
1232    if (d > 1.0) { discard; }
1233    float edge0 = clamp(uHardness, 0.0, 1.0);
1234    float x = clamp((d - edge0) / max(1.0 - edge0, 1.0e-4), 0.0, 1.0);
1235    float cov = 1.0 - (x * x * (3.0 - 2.0 * x));
1236    oFragColor = vec4(uColor.rgb, uColor.a * cov);
1237}";
1238
1239/// Checks if a shader compiled successfully. Logs an error under `std`.
1240/// (Retained for diagnostics; the version probe in `GlContextPtr::new` now does
1241/// its own status checks.)
1242#[allow(dead_code)]
1243#[allow(clippy::used_underscore_binding)] // intentional `_`-prefix (FFI/api.json pub field, or cfg-gated binding); access is deliberate
1244#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
1245fn check_shader_compile(gl_context: &GenericGlContext, shader: GLuint, _label: &str) {
1246    let mut status = [0_i32];
1247    unsafe { gl_context.get_shader_iv(shader, gl::COMPILE_STATUS, &mut status) };
1248    if status[0] != gl::TRUE as i32 {
1249        #[cfg(feature = "std")]
1250        {
1251            let log = gl_context.get_shader_info_log(shader);
1252            eprintln!("azul: {_label} shader compile error: {log}");
1253        }
1254    }
1255}
1256
1257/// Checks if a program linked successfully. Logs an error under `std`.
1258#[allow(dead_code)]
1259#[allow(clippy::used_underscore_binding)] // intentional `_`-prefix (FFI/api.json pub field, or cfg-gated binding); access is deliberate
1260#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
1261fn check_program_link(gl_context: &GenericGlContext, program: GLuint, _label: &str) {
1262    let mut status = [0_i32];
1263    unsafe { gl_context.get_program_iv(program, gl::LINK_STATUS, &mut status) };
1264    if status[0] != gl::TRUE as i32 {
1265        #[cfg(feature = "std")]
1266        {
1267            let log = gl_context.get_program_info_log(program);
1268            eprintln!("azul: {_label} program link error: {log}");
1269        }
1270    }
1271}
1272
1273/// Swap the leading `#version ...` line of a bundled shader for `version_line`
1274/// (which must include the trailing newline). The shader bodies branch on
1275/// `__VERSION__`, so only the directive needs to change between GL and GLES.
1276#[cfg(feature = "std")]
1277fn shader_with_glsl_version(src: &[u8], version_line: &[u8]) -> Vec<u8> {
1278    let body_start = src.iter().position(|&b| b == b'\n').map_or(0, |i| i + 1);
1279    let mut out = Vec::with_capacity(version_line.len() + src.len() - body_start);
1280    out.extend_from_slice(version_line);
1281    out.extend_from_slice(&src[body_start..]);
1282    out
1283}
1284
1285/// Try to compile+link a vertex+fragment program at a specific GLSL `#version`.
1286/// Returns the linked program id, or `None` (after cleanup) on ANY compile or
1287/// link failure. This is how we PROVE a GL context is actually usable and which
1288/// `#version` its driver accepts -- creating a context can succeed yet leave it
1289/// unable to compile our shaders (broken driver, or a GLES context that rejects
1290/// the desktop `#version 150`).
1291#[cfg(feature = "std")]
1292// OpenGL binding: gl::* enum constants passed to the gl API as GLint/GLenum.
1293#[allow(clippy::cast_possible_wrap)]
1294fn try_compile_program(
1295    gl_context: &GenericGlContext,
1296    vert_src: &[u8],
1297    frag_src: &[u8],
1298    version_line: &[u8],
1299    attribs: &[(u32, &str)],
1300) -> Option<GLuint> {
1301    let vs = gl_context.create_shader(gl::VERTEX_SHADER);
1302    gl_context.shader_source(
1303        vs,
1304        &[shader_with_glsl_version(vert_src, version_line).as_slice()],
1305    );
1306    gl_context.compile_shader(vs);
1307    let fs = gl_context.create_shader(gl::FRAGMENT_SHADER);
1308    gl_context.shader_source(
1309        fs,
1310        &[shader_with_glsl_version(frag_src, version_line).as_slice()],
1311    );
1312    gl_context.compile_shader(fs);
1313
1314    let mut s = [0_i32];
1315    unsafe { gl_context.get_shader_iv(vs, gl::COMPILE_STATUS, &mut s) };
1316    let vs_ok = s[0] == gl::TRUE as i32;
1317    unsafe { gl_context.get_shader_iv(fs, gl::COMPILE_STATUS, &mut s) };
1318    let fs_ok = s[0] == gl::TRUE as i32;
1319    if !vs_ok || !fs_ok {
1320        gl_context.delete_shader(vs);
1321        gl_context.delete_shader(fs);
1322        return None;
1323    }
1324
1325    let prog = gl_context.create_program();
1326    gl_context.attach_shader(prog, vs);
1327    gl_context.attach_shader(prog, fs);
1328    for (loc, name) in attribs {
1329        gl_context.bind_attrib_location(prog, *loc, name);
1330    }
1331    gl_context.link_program(prog);
1332    gl_context.delete_shader(vs);
1333    gl_context.delete_shader(fs);
1334
1335    let mut l = [0_i32];
1336    unsafe { gl_context.get_program_iv(prog, gl::LINK_STATUS, &mut l) };
1337    if l[0] == gl::TRUE as i32 {
1338        Some(prog)
1339    } else {
1340        gl_context.delete_program(prog);
1341        None
1342    }
1343}
1344
1345/// GLSL `#version` directives to try, in preference order, per context type.
1346/// The first that compiles+links the SVG shaders is used for every program.
1347const fn glsl_version_candidates(gl_type: GlType) -> &'static [&'static [u8]] {
1348    match gl_type {
1349        GlType::Gl => &[b"#version 150\n", b"#version 330\n", b"#version 140\n"],
1350        GlType::Gles => &[b"#version 300 es\n", b"#version 100\n"],
1351    }
1352}
1353
1354impl GlContextPtr {
1355    #[must_use]
1356    pub fn new(renderer_type: RendererType, gl_context: Rc<GenericGlContext>) -> Self {
1357        // Only attempt the SVG/FXAA GL shaders for a real GPU. In Software/CPU
1358        // mode nothing composites through them.
1359        //
1360        // PROVE the context is usable rather than trusting context creation: try
1361        // compiling the SVG program at each candidate `#version` for this context
1362        // type (desktop GL 1.50/3.30/1.40, or GLES 3.00/1.00) and use the first
1363        // that compiles+links for ALL programs. A GLES GPU (mobile) rejects the
1364        // desktop `#version 150`, and a broken driver rejects everything -- in the
1365        // latter case all program IDs stay 0 and `is_gl_usable()` returns false so
1366        // the window can fall back to CPU rendering.
1367        #[cfg(feature = "std")]
1368        let (
1369            svg_program_id,
1370            svg_multicolor_program_id,
1371            fxaa_program_id,
1372            brush_program_id,
1373            glsl_version,
1374        ) = if matches!(renderer_type, RendererType::Hardware) {
1375            use crate::gl_fxaa::{FXAA_FRAGMENT_SHADER, FXAA_VERTEX_SHADER};
1376            let gl_type: GlType = gl_context.get_type().into();
1377            // Probe via the SVG program; the first version that links wins.
1378            let mut svg = 0;
1379            let mut chosen: Option<&'static [u8]> = None;
1380            for ver in glsl_version_candidates(gl_type) {
1381                if let Some(p) = try_compile_program(
1382                    &gl_context,
1383                    SVG_VERTEX_SHADER,
1384                    SVG_FRAGMENT_SHADER,
1385                    ver,
1386                    &[(0, "vAttrXY")],
1387                ) {
1388                    svg = p;
1389                    chosen = Some(ver);
1390                    break;
1391                }
1392            }
1393            chosen.map_or_else(|| {
1394                    eprintln!(
1395                        "azul: GL context UNUSABLE -- no GLSL version ({gl_type:?}) compiled the SVG \
1396                         shaders; the window should fall back to CPU rendering (is_gl_usable()=false)"
1397                    );
1398                    (0, 0, 0, 0, AzString::from_const_str(""))
1399                }, |ver| {
1400                    // "150" / "300 es": the directive minus "#version " and newline.
1401                    let ver_str: AzString = core::str::from_utf8(ver)
1402                        .unwrap_or("")
1403                        .trim()
1404                        .trim_start_matches("#version ")
1405                        .into();
1406                    eprintln!(
1407                        "azul: GL usable -- shaders compiled at GLSL {} ({:?})",
1408                        ver_str.as_str(),
1409                        gl_type
1410                    );
1411                    let mc = try_compile_program(
1412                        &gl_context, SVG_MULTICOLOR_VERTEX_SHADER, SVG_MULTICOLOR_FRAGMENT_SHADER,
1413                        ver, &[(0, "vAttrXY"), (1, "vColor")],
1414                    ).unwrap_or(0);
1415                    let fxaa = try_compile_program(
1416                        &gl_context, FXAA_VERTEX_SHADER, FXAA_FRAGMENT_SHADER, ver, &[(0, "vAttrXY")],
1417                    ).unwrap_or(0);
1418                    let brush = try_compile_program(
1419                        &gl_context, BRUSH_VERTEX_SHADER, BRUSH_FRAGMENT_SHADER, ver,
1420                        &[(0, "aPos"), (1, "aUv")],
1421                    ).unwrap_or(0);
1422                    (svg, mc, fxaa, brush, ver_str)
1423                })
1424        } else {
1425            (0, 0, 0, 0, AzString::from_const_str(""))
1426        };
1427        // no_std build keeps the original behavior (no probe / no shaders).
1428        #[cfg(not(feature = "std"))]
1429        let (
1430            svg_program_id,
1431            svg_multicolor_program_id,
1432            fxaa_program_id,
1433            brush_program_id,
1434            glsl_version,
1435        ) = (0u32, 0u32, 0u32, 0u32, AzString::from_const_str(""));
1436
1437        Self {
1438            ptr: ManuallyDrop::new(Box::new(Rc::new(GlContextPtrInner {
1439                svg_shader: svg_program_id,
1440                svg_multicolor_shader: svg_multicolor_program_id,
1441                fxaa_shader: fxaa_program_id,
1442                brush_shader: brush_program_id,
1443                glsl_version,
1444                ptr: gl_context,
1445            }))),
1446            renderer_type,
1447            run_destructor: true,
1448        }
1449    }
1450
1451    #[must_use]
1452    pub fn get(&self) -> &Rc<GenericGlContext> {
1453        &self.ptr.ptr
1454    }
1455    fn as_usize(&self) -> usize {
1456        (Rc::as_ptr(&self.ptr.ptr) as *const c_void) as usize
1457    }
1458}
1459
1460// This impl is the OpenGL API wrapper: every method mirrors a C/gleam GL call and
1461// takes the C-ABI argument types (GlVoidPtrConst, *VecRef, …) BY VALUE to match that
1462// ABI/FFI calling convention. Switching them to references would break the contract,
1463// so needless_pass_by_value is allowed for the whole GL-binding impl.
1464#[allow(clippy::needless_pass_by_value)]
1465impl GlContextPtr {
1466    #[must_use]
1467    pub fn get_type(&self) -> GlType {
1468        self.get().get_type().into()
1469    }
1470    pub fn buffer_data_untyped(
1471        &self,
1472        target: GLenum,
1473        size: GLsizeiptr,
1474        data: GlVoidPtrConst,
1475        usage: GLenum,
1476    ) {
1477        self.get()
1478            .buffer_data_untyped(target, size, data.ptr, usage);
1479    }
1480    pub fn buffer_sub_data_untyped(
1481        &self,
1482        target: GLenum,
1483        offset: isize,
1484        size: GLsizeiptr,
1485        data: GlVoidPtrConst,
1486    ) {
1487        self.get()
1488            .buffer_sub_data_untyped(target, offset, size, data.ptr);
1489    }
1490    #[must_use]
1491    pub fn map_buffer(&self, target: GLenum, access: GLbitfield) -> GlVoidPtrMut {
1492        GlVoidPtrMut {
1493            ptr: self.get().map_buffer(target, access),
1494        }
1495    }
1496    #[must_use]
1497    pub fn map_buffer_range(
1498        &self,
1499        target: GLenum,
1500        offset: GLintptr,
1501        length: GLsizeiptr,
1502        access: GLbitfield,
1503    ) -> GlVoidPtrMut {
1504        GlVoidPtrMut {
1505            ptr: self.get().map_buffer_range(target, offset, length, access),
1506        }
1507    }
1508    #[must_use]
1509    pub fn unmap_buffer(&self, target: GLenum) -> GLboolean {
1510        self.get().unmap_buffer(target)
1511    }
1512    pub fn tex_buffer(&self, target: GLenum, internal_format: GLenum, buffer: GLuint) {
1513        self.get().tex_buffer(target, internal_format, buffer);
1514    }
1515    pub fn shader_source(&self, shader: GLuint, strings: StringVec) {
1516        fn str_to_bytes(input: &str) -> Vec<u8> {
1517            let mut v: Vec<u8> = input.into();
1518            v.push(0);
1519            v
1520        }
1521        let shaders_as_bytes = strings
1522            .iter()
1523            .map(|s| str_to_bytes(s.as_str()))
1524            .collect::<Vec<_>>();
1525        let shaders_as_bytes = shaders_as_bytes
1526            .iter()
1527            .map(AsRef::as_ref)
1528            .collect::<Vec<_>>();
1529        self.get().shader_source(shader, &shaders_as_bytes);
1530    }
1531    pub fn read_buffer(&self, mode: GLenum) {
1532        self.get().read_buffer(mode);
1533    }
1534    pub fn read_pixels_into_buffer(
1535        &self,
1536        x: GLint,
1537        y: GLint,
1538        width: GLsizei,
1539        height: GLsizei,
1540        format: GLenum,
1541        pixel_type: GLenum,
1542        mut dst_buffer: U8VecRefMut,
1543    ) {
1544        self.get().read_pixels_into_buffer(
1545            x,
1546            y,
1547            width,
1548            height,
1549            format,
1550            pixel_type,
1551            dst_buffer.as_mut_slice(),
1552        );
1553    }
1554    #[must_use]
1555    pub fn read_pixels(
1556        &self,
1557        x: GLint,
1558        y: GLint,
1559        width: GLsizei,
1560        height: GLsizei,
1561        format: GLenum,
1562        pixel_type: GLenum,
1563    ) -> U8Vec {
1564        // gl-context-loader's own read_pixels sizes the buffer as
1565        // width*height*bytes_per_component and OMITS the format's channel count, so a
1566        // 2x3 RGBA/UNSIGNED_BYTE read allocates 6 bytes instead of 24 — a heap overflow
1567        // once a real driver writes the pixels. Size it correctly from format + type and
1568        // read into our own buffer. (Raw GL enum values so this doesn't depend on which
1569        // constants the gl module happens to re-export.)
1570        let channels: usize = match format {
1571            // RED, ALPHA, LUMINANCE, DEPTH_COMPONENT, STENCIL_INDEX, RED_INTEGER
1572            0x1903 | 0x1906 | 0x1909 | 0x1902 | 0x1901 | 0x8D94 => 1,
1573            // RG, LUMINANCE_ALPHA, RG_INTEGER, DEPTH_STENCIL
1574            0x8227 | 0x190A | 0x8228 | 0x84F9 => 2,
1575            // RGB, BGR, RGB_INTEGER
1576            0x1907 | 0x80E0 | 0x8D98 => 3,
1577            // RGBA, BGRA, RGBA_INTEGER, and a conservative default
1578            _ => 4,
1579        };
1580        let bytes_per_component: usize = match pixel_type {
1581            0x1400 | 0x1401 => 1,          // BYTE, UNSIGNED_BYTE
1582            0x1402 | 0x1403 | 0x140B => 2, // SHORT, UNSIGNED_SHORT, HALF_FLOAT
1583            _ => 4,                        // INT, UNSIGNED_INT, FLOAT, and default
1584        };
1585        // width/height are clamped to >= 0 before the cast, so no sign is lost.
1586        #[allow(clippy::cast_sign_loss)]
1587        let len = (width.max(0) as usize)
1588            .saturating_mul(height.max(0) as usize)
1589            .saturating_mul(channels)
1590            .saturating_mul(bytes_per_component);
1591        let mut buf = vec![0u8; len];
1592        self.get().read_pixels_into_buffer(
1593            x,
1594            y,
1595            width,
1596            height,
1597            format,
1598            pixel_type,
1599            buf.as_mut_slice(),
1600        );
1601        buf.into()
1602    }
1603    pub fn read_pixels_into_pbo(
1604        &self,
1605        x: GLint,
1606        y: GLint,
1607        width: GLsizei,
1608        height: GLsizei,
1609        format: GLenum,
1610        pixel_type: GLenum,
1611    ) {
1612        unsafe {
1613            self.get()
1614                .read_pixels_into_pbo(x, y, width, height, format, pixel_type);
1615        }
1616    }
1617    pub fn sample_coverage(&self, value: GLclampf, invert: bool) {
1618        self.get().sample_coverage(value, invert);
1619    }
1620    pub fn polygon_offset(&self, factor: GLfloat, units: GLfloat) {
1621        self.get().polygon_offset(factor, units);
1622    }
1623    pub fn pixel_store_i(&self, name: GLenum, param: GLint) {
1624        self.get().pixel_store_i(name, param);
1625    }
1626    #[must_use]
1627    pub fn gen_buffers(&self, n: GLsizei) -> GLuintVec {
1628        self.get().gen_buffers(n).into()
1629    }
1630    #[must_use]
1631    pub fn gen_renderbuffers(&self, n: GLsizei) -> GLuintVec {
1632        self.get().gen_renderbuffers(n).into()
1633    }
1634    #[must_use]
1635    pub fn gen_framebuffers(&self, n: GLsizei) -> GLuintVec {
1636        self.get().gen_framebuffers(n).into()
1637    }
1638    #[must_use]
1639    pub fn gen_textures(&self, n: GLsizei) -> GLuintVec {
1640        self.get().gen_textures(n).into()
1641    }
1642    #[must_use]
1643    pub fn gen_vertex_arrays(&self, n: GLsizei) -> GLuintVec {
1644        self.get().gen_vertex_arrays(n).into()
1645    }
1646    #[must_use]
1647    pub fn gen_queries(&self, n: GLsizei) -> GLuintVec {
1648        self.get().gen_queries(n).into()
1649    }
1650    pub fn begin_query(&self, target: GLenum, id: GLuint) {
1651        self.get().begin_query(target, id);
1652    }
1653    pub fn end_query(&self, target: GLenum) {
1654        self.get().end_query(target);
1655    }
1656    pub fn query_counter(&self, id: GLuint, target: GLenum) {
1657        self.get().query_counter(id, target);
1658    }
1659    #[must_use]
1660    pub fn get_query_object_iv(&self, id: GLuint, pname: GLenum) -> i32 {
1661        self.get().get_query_object_iv(id, pname)
1662    }
1663    #[must_use]
1664    pub fn get_query_object_uiv(&self, id: GLuint, pname: GLenum) -> u32 {
1665        self.get().get_query_object_uiv(id, pname)
1666    }
1667    #[must_use]
1668    pub fn get_query_object_i64v(&self, id: GLuint, pname: GLenum) -> i64 {
1669        self.get().get_query_object_i64v(id, pname)
1670    }
1671    #[must_use]
1672    pub fn get_query_object_ui64v(&self, id: GLuint, pname: GLenum) -> u64 {
1673        self.get().get_query_object_ui64v(id, pname)
1674    }
1675    pub fn delete_queries(&self, queries: GLuintVecRef) {
1676        self.get().delete_queries(queries.as_slice());
1677    }
1678    pub fn delete_vertex_arrays(&self, vertex_arrays: GLuintVecRef) {
1679        self.get().delete_vertex_arrays(vertex_arrays.as_slice());
1680    }
1681    pub fn delete_buffers(&self, buffers: GLuintVecRef) {
1682        self.get().delete_buffers(buffers.as_slice());
1683    }
1684    pub fn delete_renderbuffers(&self, renderbuffers: GLuintVecRef) {
1685        self.get().delete_renderbuffers(renderbuffers.as_slice());
1686    }
1687    pub fn delete_framebuffers(&self, framebuffers: GLuintVecRef) {
1688        self.get().delete_framebuffers(framebuffers.as_slice());
1689    }
1690    pub fn delete_textures(&self, textures: GLuintVecRef) {
1691        self.get().delete_textures(textures.as_slice());
1692    }
1693    pub fn framebuffer_renderbuffer(
1694        &self,
1695        target: GLenum,
1696        attachment: GLenum,
1697        renderbuffertarget: GLenum,
1698        renderbuffer: GLuint,
1699    ) {
1700        self.get()
1701            .framebuffer_renderbuffer(target, attachment, renderbuffertarget, renderbuffer);
1702    }
1703    pub fn renderbuffer_storage(
1704        &self,
1705        target: GLenum,
1706        internalformat: GLenum,
1707        width: GLsizei,
1708        height: GLsizei,
1709    ) {
1710        self.get()
1711            .renderbuffer_storage(target, internalformat, width, height);
1712    }
1713    pub fn depth_func(&self, func: GLenum) {
1714        self.get().depth_func(func);
1715    }
1716    pub fn active_texture(&self, texture: GLenum) {
1717        self.get().active_texture(texture);
1718    }
1719    pub fn attach_shader(&self, program: GLuint, shader: GLuint) {
1720        self.get().attach_shader(program, shader);
1721    }
1722    pub fn bind_attrib_location(&self, program: GLuint, index: GLuint, name: &str) {
1723        self.get().bind_attrib_location(program, index, name);
1724    }
1725    pub fn get_uniform_iv(&self, program: GLuint, location: GLint, mut result: GLintVecRefMut) {
1726        unsafe {
1727            self.get()
1728                .get_uniform_iv(program, location, result.as_mut_slice());
1729        }
1730    }
1731    pub fn get_uniform_fv(&self, program: GLuint, location: GLint, mut result: GLfloatVecRefMut) {
1732        unsafe {
1733            self.get()
1734                .get_uniform_fv(program, location, result.as_mut_slice());
1735        }
1736    }
1737    #[must_use]
1738    pub fn get_uniform_block_index(&self, program: GLuint, name: &str) -> GLuint {
1739        self.get().get_uniform_block_index(program, name)
1740    }
1741    #[must_use]
1742    pub fn get_uniform_indices(&self, program: GLuint, names: RefstrVecRef) -> GLuintVec {
1743        let names_vec = names
1744            .as_slice()
1745            .iter()
1746            .map(Refstr::as_str)
1747            .collect::<Vec<_>>();
1748        self.get().get_uniform_indices(program, &names_vec).into()
1749    }
1750    pub fn bind_buffer_base(&self, target: GLenum, index: GLuint, buffer: GLuint) {
1751        self.get().bind_buffer_base(target, index, buffer);
1752    }
1753    pub fn bind_buffer_range(
1754        &self,
1755        target: GLenum,
1756        index: GLuint,
1757        buffer: GLuint,
1758        offset: GLintptr,
1759        size: GLsizeiptr,
1760    ) {
1761        self.get()
1762            .bind_buffer_range(target, index, buffer, offset, size);
1763    }
1764    pub fn uniform_block_binding(
1765        &self,
1766        program: GLuint,
1767        uniform_block_index: GLuint,
1768        uniform_block_binding: GLuint,
1769    ) {
1770        self.get()
1771            .uniform_block_binding(program, uniform_block_index, uniform_block_binding);
1772    }
1773    pub fn bind_buffer(&self, target: GLenum, buffer: GLuint) {
1774        self.get().bind_buffer(target, buffer);
1775    }
1776    pub fn bind_vertex_array(&self, vao: GLuint) {
1777        self.get().bind_vertex_array(vao);
1778    }
1779    pub fn bind_renderbuffer(&self, target: GLenum, renderbuffer: GLuint) {
1780        self.get().bind_renderbuffer(target, renderbuffer);
1781    }
1782    pub fn bind_framebuffer(&self, target: GLenum, framebuffer: GLuint) {
1783        self.get().bind_framebuffer(target, framebuffer);
1784    }
1785    pub fn bind_texture(&self, target: GLenum, texture: GLuint) {
1786        self.get().bind_texture(target, texture);
1787    }
1788    pub fn draw_buffers(&self, bufs: GLenumVecRef) {
1789        self.get().draw_buffers(bufs.as_slice());
1790    }
1791    pub fn tex_image_2d(
1792        &self,
1793        target: GLenum,
1794        level: GLint,
1795        internal_format: GLint,
1796        width: GLsizei,
1797        height: GLsizei,
1798        border: GLint,
1799        format: GLenum,
1800        ty: GLenum,
1801        opt_data: OptionU8VecRef,
1802    ) {
1803        let opt_data = opt_data.as_option();
1804        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
1805        self.get().tex_image_2d(
1806            target,
1807            level,
1808            internal_format,
1809            width,
1810            height,
1811            border,
1812            format,
1813            ty,
1814            opt_data,
1815        );
1816    }
1817    pub fn compressed_tex_image_2d(
1818        &self,
1819        target: GLenum,
1820        level: GLint,
1821        internal_format: GLenum,
1822        width: GLsizei,
1823        height: GLsizei,
1824        border: GLint,
1825        data: U8VecRef,
1826    ) {
1827        self.get().compressed_tex_image_2d(
1828            target,
1829            level,
1830            internal_format,
1831            width,
1832            height,
1833            border,
1834            data.as_slice(),
1835        );
1836    }
1837    pub fn compressed_tex_sub_image_2d(
1838        &self,
1839        target: GLenum,
1840        level: GLint,
1841        xoffset: GLint,
1842        yoffset: GLint,
1843        width: GLsizei,
1844        height: GLsizei,
1845        format: GLenum,
1846        data: U8VecRef,
1847    ) {
1848        self.get().compressed_tex_sub_image_2d(
1849            target,
1850            level,
1851            xoffset,
1852            yoffset,
1853            width,
1854            height,
1855            format,
1856            data.as_slice(),
1857        );
1858    }
1859    pub fn tex_image_3d(
1860        &self,
1861        target: GLenum,
1862        level: GLint,
1863        internal_format: GLint,
1864        width: GLsizei,
1865        height: GLsizei,
1866        depth: GLsizei,
1867        border: GLint,
1868        format: GLenum,
1869        ty: GLenum,
1870        opt_data: OptionU8VecRef,
1871    ) {
1872        let opt_data = opt_data.as_option();
1873        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
1874        self.get().tex_image_3d(
1875            target,
1876            level,
1877            internal_format,
1878            width,
1879            height,
1880            depth,
1881            border,
1882            format,
1883            ty,
1884            opt_data,
1885        );
1886    }
1887    pub fn copy_tex_image_2d(
1888        &self,
1889        target: GLenum,
1890        level: GLint,
1891        internal_format: GLenum,
1892        x: GLint,
1893        y: GLint,
1894        width: GLsizei,
1895        height: GLsizei,
1896        border: GLint,
1897    ) {
1898        self.get()
1899            .copy_tex_image_2d(target, level, internal_format, x, y, width, height, border);
1900    }
1901    pub fn copy_tex_sub_image_2d(
1902        &self,
1903        target: GLenum,
1904        level: GLint,
1905        xoffset: GLint,
1906        yoffset: GLint,
1907        x: GLint,
1908        y: GLint,
1909        width: GLsizei,
1910        height: GLsizei,
1911    ) {
1912        self.get()
1913            .copy_tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height);
1914    }
1915    pub fn copy_tex_sub_image_3d(
1916        &self,
1917        target: GLenum,
1918        level: GLint,
1919        xoffset: GLint,
1920        yoffset: GLint,
1921        zoffset: GLint,
1922        x: GLint,
1923        y: GLint,
1924        width: GLsizei,
1925        height: GLsizei,
1926    ) {
1927        self.get().copy_tex_sub_image_3d(
1928            target, level, xoffset, yoffset, zoffset, x, y, width, height,
1929        );
1930    }
1931    pub fn tex_sub_image_2d(
1932        &self,
1933        target: GLenum,
1934        level: GLint,
1935        xoffset: GLint,
1936        yoffset: GLint,
1937        width: GLsizei,
1938        height: GLsizei,
1939        format: GLenum,
1940        ty: GLenum,
1941        data: U8VecRef,
1942    ) {
1943        self.get().tex_sub_image_2d(
1944            target,
1945            level,
1946            xoffset,
1947            yoffset,
1948            width,
1949            height,
1950            format,
1951            ty,
1952            data.as_slice(),
1953        );
1954    }
1955    pub fn tex_sub_image_2d_pbo(
1956        &self,
1957        target: GLenum,
1958        level: GLint,
1959        xoffset: GLint,
1960        yoffset: GLint,
1961        width: GLsizei,
1962        height: GLsizei,
1963        format: GLenum,
1964        ty: GLenum,
1965        offset: usize,
1966    ) {
1967        self.get().tex_sub_image_2d_pbo(
1968            target, level, xoffset, yoffset, width, height, format, ty, offset,
1969        );
1970    }
1971    pub fn tex_sub_image_3d(
1972        &self,
1973        target: GLenum,
1974        level: GLint,
1975        xoffset: GLint,
1976        yoffset: GLint,
1977        zoffset: GLint,
1978        width: GLsizei,
1979        height: GLsizei,
1980        depth: GLsizei,
1981        format: GLenum,
1982        ty: GLenum,
1983        data: U8VecRef,
1984    ) {
1985        self.get().tex_sub_image_3d(
1986            target,
1987            level,
1988            xoffset,
1989            yoffset,
1990            zoffset,
1991            width,
1992            height,
1993            depth,
1994            format,
1995            ty,
1996            data.as_slice(),
1997        );
1998    }
1999    pub fn tex_sub_image_3d_pbo(
2000        &self,
2001        target: GLenum,
2002        level: GLint,
2003        xoffset: GLint,
2004        yoffset: GLint,
2005        zoffset: GLint,
2006        width: GLsizei,
2007        height: GLsizei,
2008        depth: GLsizei,
2009        format: GLenum,
2010        ty: GLenum,
2011        offset: usize,
2012    ) {
2013        self.get().tex_sub_image_3d_pbo(
2014            target, level, xoffset, yoffset, zoffset, width, height, depth, format, ty, offset,
2015        );
2016    }
2017    pub fn tex_storage_2d(
2018        &self,
2019        target: GLenum,
2020        levels: GLint,
2021        internal_format: GLenum,
2022        width: GLsizei,
2023        height: GLsizei,
2024    ) {
2025        self.get()
2026            .tex_storage_2d(target, levels, internal_format, width, height);
2027    }
2028    pub fn tex_storage_3d(
2029        &self,
2030        target: GLenum,
2031        levels: GLint,
2032        internal_format: GLenum,
2033        width: GLsizei,
2034        height: GLsizei,
2035        depth: GLsizei,
2036    ) {
2037        self.get()
2038            .tex_storage_3d(target, levels, internal_format, width, height, depth);
2039    }
2040    pub fn get_tex_image_into_buffer(
2041        &self,
2042        target: GLenum,
2043        level: GLint,
2044        format: GLenum,
2045        ty: GLenum,
2046        mut output: U8VecRefMut,
2047    ) {
2048        self.get()
2049            .get_tex_image_into_buffer(target, level, format, ty, output.as_mut_slice());
2050    }
2051    pub fn copy_image_sub_data(
2052        &self,
2053        src_name: GLuint,
2054        src_target: GLenum,
2055        src_level: GLint,
2056        src_x: GLint,
2057        src_y: GLint,
2058        src_z: GLint,
2059        dst_name: GLuint,
2060        dst_target: GLenum,
2061        dst_level: GLint,
2062        dst_x: GLint,
2063        dst_y: GLint,
2064        dst_z: GLint,
2065        src_width: GLsizei,
2066        src_height: GLsizei,
2067        src_depth: GLsizei,
2068    ) {
2069        unsafe {
2070            self.get().copy_image_sub_data(
2071                src_name, src_target, src_level, src_x, src_y, src_z, dst_name, dst_target,
2072                dst_level, dst_x, dst_y, dst_z, src_width, src_height, src_depth,
2073            );
2074        }
2075    }
2076    pub fn invalidate_framebuffer(&self, target: GLenum, attachments: GLenumVecRef) {
2077        self.get()
2078            .invalidate_framebuffer(target, attachments.as_slice());
2079    }
2080    pub fn invalidate_sub_framebuffer(
2081        &self,
2082        target: GLenum,
2083        attachments: GLenumVecRef,
2084        xoffset: GLint,
2085        yoffset: GLint,
2086        width: GLsizei,
2087        height: GLsizei,
2088    ) {
2089        self.get().invalidate_sub_framebuffer(
2090            target,
2091            attachments.as_slice(),
2092            xoffset,
2093            yoffset,
2094            width,
2095            height,
2096        );
2097    }
2098    pub fn get_integer_v(&self, name: GLenum, mut result: GLintVecRefMut) {
2099        unsafe { self.get().get_integer_v(name, result.as_mut_slice()) }
2100    }
2101    pub fn get_integer_64v(&self, name: GLenum, mut result: GLint64VecRefMut) {
2102        unsafe { self.get().get_integer_64v(name, result.as_mut_slice()) }
2103    }
2104    pub fn get_integer_iv(&self, name: GLenum, index: GLuint, mut result: GLintVecRefMut) {
2105        unsafe {
2106            self.get()
2107                .get_integer_iv(name, index, result.as_mut_slice());
2108        }
2109    }
2110    pub fn get_integer_64iv(&self, name: GLenum, index: GLuint, mut result: GLint64VecRefMut) {
2111        unsafe {
2112            self.get()
2113                .get_integer_64iv(name, index, result.as_mut_slice());
2114        }
2115    }
2116    pub fn get_boolean_v(&self, name: GLenum, mut result: GLbooleanVecRefMut) {
2117        unsafe { self.get().get_boolean_v(name, result.as_mut_slice()) }
2118    }
2119    pub fn get_float_v(&self, name: GLenum, mut result: GLfloatVecRefMut) {
2120        unsafe { self.get().get_float_v(name, result.as_mut_slice()) }
2121    }
2122    #[must_use]
2123    pub fn get_framebuffer_attachment_parameter_iv(
2124        &self,
2125        target: GLenum,
2126        attachment: GLenum,
2127        pname: GLenum,
2128    ) -> GLint {
2129        self.get()
2130            .get_framebuffer_attachment_parameter_iv(target, attachment, pname)
2131    }
2132    #[must_use]
2133    pub fn get_renderbuffer_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint {
2134        self.get().get_renderbuffer_parameter_iv(target, pname)
2135    }
2136    #[must_use]
2137    pub fn get_tex_parameter_iv(&self, target: GLenum, name: GLenum) -> GLint {
2138        self.get().get_tex_parameter_iv(target, name)
2139    }
2140    #[must_use]
2141    pub fn get_tex_parameter_fv(&self, target: GLenum, name: GLenum) -> GLfloat {
2142        self.get().get_tex_parameter_fv(target, name)
2143    }
2144    pub fn tex_parameter_i(&self, target: GLenum, pname: GLenum, param: GLint) {
2145        self.get().tex_parameter_i(target, pname, param);
2146    }
2147    pub fn tex_parameter_f(&self, target: GLenum, pname: GLenum, param: GLfloat) {
2148        self.get().tex_parameter_f(target, pname, param);
2149    }
2150    pub fn framebuffer_texture_2d(
2151        &self,
2152        target: GLenum,
2153        attachment: GLenum,
2154        textarget: GLenum,
2155        texture: GLuint,
2156        level: GLint,
2157    ) {
2158        self.get()
2159            .framebuffer_texture_2d(target, attachment, textarget, texture, level);
2160    }
2161    pub fn framebuffer_texture_layer(
2162        &self,
2163        target: GLenum,
2164        attachment: GLenum,
2165        texture: GLuint,
2166        level: GLint,
2167        layer: GLint,
2168    ) {
2169        self.get()
2170            .framebuffer_texture_layer(target, attachment, texture, level, layer);
2171    }
2172    #[allow(clippy::similar_names)] // domain-standard coordinate/control-point names
2173    pub fn blit_framebuffer(
2174        &self,
2175        src_x0: GLint,
2176        src_y0: GLint,
2177        src_x1: GLint,
2178        src_y1: GLint,
2179        dst_x0: GLint,
2180        dst_y0: GLint,
2181        dst_x1: GLint,
2182        dst_y1: GLint,
2183        mask: GLbitfield,
2184        filter: GLenum,
2185    ) {
2186        self.get().blit_framebuffer(
2187            src_x0, src_y0, src_x1, src_y1, dst_x0, dst_y0, dst_x1, dst_y1, mask, filter,
2188        );
2189    }
2190    pub fn vertex_attrib_4f(&self, index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
2191        self.get().vertex_attrib_4f(index, x, y, z, w);
2192    }
2193    pub fn vertex_attrib_pointer_f32(
2194        &self,
2195        index: GLuint,
2196        size: GLint,
2197        normalized: bool,
2198        stride: GLsizei,
2199        offset: GLuint,
2200    ) {
2201        self.get()
2202            .vertex_attrib_pointer_f32(index, size, normalized, stride, offset);
2203    }
2204    pub fn vertex_attrib_pointer(
2205        &self,
2206        index: GLuint,
2207        size: GLint,
2208        type_: GLenum,
2209        normalized: bool,
2210        stride: GLsizei,
2211        offset: GLuint,
2212    ) {
2213        self.get()
2214            .vertex_attrib_pointer(index, size, type_, normalized, stride, offset);
2215    }
2216    pub fn vertex_attrib_i_pointer(
2217        &self,
2218        index: GLuint,
2219        size: GLint,
2220        type_: GLenum,
2221        stride: GLsizei,
2222        offset: GLuint,
2223    ) {
2224        self.get()
2225            .vertex_attrib_i_pointer(index, size, type_, stride, offset);
2226    }
2227    pub fn vertex_attrib_divisor(&self, index: GLuint, divisor: GLuint) {
2228        self.get().vertex_attrib_divisor(index, divisor);
2229    }
2230    pub fn viewport(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
2231        self.get().viewport(x, y, width, height);
2232    }
2233    pub fn scissor(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
2234        self.get().scissor(x, y, width, height);
2235    }
2236    pub fn line_width(&self, width: GLfloat) {
2237        self.get().line_width(width);
2238    }
2239    pub fn use_program(&self, program: GLuint) {
2240        self.get().use_program(program);
2241    }
2242    pub fn validate_program(&self, program: GLuint) {
2243        self.get().validate_program(program);
2244    }
2245    pub fn draw_arrays(&self, mode: GLenum, first: GLint, count: GLsizei) {
2246        self.get().draw_arrays(mode, first, count);
2247    }
2248    pub fn draw_arrays_instanced(
2249        &self,
2250        mode: GLenum,
2251        first: GLint,
2252        count: GLsizei,
2253        primcount: GLsizei,
2254    ) {
2255        self.get()
2256            .draw_arrays_instanced(mode, first, count, primcount);
2257    }
2258    pub fn draw_elements(
2259        &self,
2260        mode: GLenum,
2261        count: GLsizei,
2262        element_type: GLenum,
2263        indices_offset: GLuint,
2264    ) {
2265        self.get()
2266            .draw_elements(mode, count, element_type, indices_offset);
2267    }
2268    pub fn draw_elements_instanced(
2269        &self,
2270        mode: GLenum,
2271        count: GLsizei,
2272        element_type: GLenum,
2273        indices_offset: GLuint,
2274        primcount: GLsizei,
2275    ) {
2276        self.get()
2277            .draw_elements_instanced(mode, count, element_type, indices_offset, primcount);
2278    }
2279    pub fn blend_color(&self, r: f32, g: f32, b: f32, a: f32) {
2280        self.get().blend_color(r, g, b, a);
2281    }
2282    pub fn blend_func(&self, sfactor: GLenum, dfactor: GLenum) {
2283        self.get().blend_func(sfactor, dfactor);
2284    }
2285    pub fn blend_func_separate(
2286        &self,
2287        src_rgb: GLenum,
2288        dest_rgb: GLenum,
2289        src_alpha: GLenum,
2290        dest_alpha: GLenum,
2291    ) {
2292        self.get()
2293            .blend_func_separate(src_rgb, dest_rgb, src_alpha, dest_alpha);
2294    }
2295    pub fn blend_equation(&self, mode: GLenum) {
2296        self.get().blend_equation(mode);
2297    }
2298    pub fn blend_equation_separate(&self, mode_rgb: GLenum, mode_alpha: GLenum) {
2299        self.get().blend_equation_separate(mode_rgb, mode_alpha);
2300    }
2301    // mirrors glColorMask(GLboolean, GLboolean, GLboolean, GLboolean) — the four
2302    // RGBA write-mask flags are the GL API, not a refactorable bool soup.
2303    #[allow(clippy::fn_params_excessive_bools)]
2304    pub fn color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
2305        self.get().color_mask(r, g, b, a);
2306    }
2307    pub fn cull_face(&self, mode: GLenum) {
2308        self.get().cull_face(mode);
2309    }
2310    pub fn front_face(&self, mode: GLenum) {
2311        self.get().front_face(mode);
2312    }
2313    pub fn enable(&self, cap: GLenum) {
2314        self.get().enable(cap);
2315    }
2316    pub fn disable(&self, cap: GLenum) {
2317        self.get().disable(cap);
2318    }
2319    pub fn hint(&self, param_name: GLenum, param_val: GLenum) {
2320        self.get().hint(param_name, param_val);
2321    }
2322    #[must_use]
2323    pub fn is_enabled(&self, cap: GLenum) -> GLboolean {
2324        self.get().is_enabled(cap)
2325    }
2326    #[must_use]
2327    pub fn is_shader(&self, shader: GLuint) -> GLboolean {
2328        self.get().is_shader(shader)
2329    }
2330    #[must_use]
2331    pub fn is_texture(&self, texture: GLenum) -> GLboolean {
2332        self.get().is_texture(texture)
2333    }
2334    #[must_use]
2335    pub fn is_framebuffer(&self, framebuffer: GLenum) -> GLboolean {
2336        self.get().is_framebuffer(framebuffer)
2337    }
2338    #[must_use]
2339    pub fn is_renderbuffer(&self, renderbuffer: GLenum) -> GLboolean {
2340        self.get().is_renderbuffer(renderbuffer)
2341    }
2342    #[must_use]
2343    pub fn check_frame_buffer_status(&self, target: GLenum) -> GLenum {
2344        self.get().check_frame_buffer_status(target)
2345    }
2346    pub fn enable_vertex_attrib_array(&self, index: GLuint) {
2347        self.get().enable_vertex_attrib_array(index);
2348    }
2349    pub fn disable_vertex_attrib_array(&self, index: GLuint) {
2350        self.get().disable_vertex_attrib_array(index);
2351    }
2352    pub fn uniform_1f(&self, location: GLint, v0: GLfloat) {
2353        self.get().uniform_1f(location, v0);
2354    }
2355    pub fn uniform_1fv(&self, location: GLint, values: F32VecRef) {
2356        self.get().uniform_1fv(location, values.as_slice());
2357    }
2358    pub fn uniform_1i(&self, location: GLint, v0: GLint) {
2359        self.get().uniform_1i(location, v0);
2360    }
2361    pub fn uniform_1iv(&self, location: GLint, values: I32VecRef) {
2362        self.get().uniform_1iv(location, values.as_slice());
2363    }
2364    pub fn uniform_1ui(&self, location: GLint, v0: GLuint) {
2365        self.get().uniform_1ui(location, v0);
2366    }
2367    pub fn uniform_2f(&self, location: GLint, v0: GLfloat, v1: GLfloat) {
2368        self.get().uniform_2f(location, v0, v1);
2369    }
2370    pub fn uniform_2fv(&self, location: GLint, values: F32VecRef) {
2371        self.get().uniform_2fv(location, values.as_slice());
2372    }
2373    pub fn uniform_2i(&self, location: GLint, v0: GLint, v1: GLint) {
2374        self.get().uniform_2i(location, v0, v1);
2375    }
2376    pub fn uniform_2iv(&self, location: GLint, values: I32VecRef) {
2377        self.get().uniform_2iv(location, values.as_slice());
2378    }
2379    pub fn uniform_2ui(&self, location: GLint, v0: GLuint, v1: GLuint) {
2380        self.get().uniform_2ui(location, v0, v1);
2381    }
2382    pub fn uniform_3f(&self, location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat) {
2383        self.get().uniform_3f(location, v0, v1, v2);
2384    }
2385    pub fn uniform_3fv(&self, location: GLint, values: F32VecRef) {
2386        self.get().uniform_3fv(location, values.as_slice());
2387    }
2388    pub fn uniform_3i(&self, location: GLint, v0: GLint, v1: GLint, v2: GLint) {
2389        self.get().uniform_3i(location, v0, v1, v2);
2390    }
2391    pub fn uniform_3iv(&self, location: GLint, values: I32VecRef) {
2392        self.get().uniform_3iv(location, values.as_slice());
2393    }
2394    pub fn uniform_3ui(&self, location: GLint, v0: GLuint, v1: GLuint, v2: GLuint) {
2395        self.get().uniform_3ui(location, v0, v1, v2);
2396    }
2397    pub fn uniform_4f(&self, location: GLint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
2398        self.get().uniform_4f(location, x, y, z, w);
2399    }
2400    pub fn uniform_4i(&self, location: GLint, x: GLint, y: GLint, z: GLint, w: GLint) {
2401        self.get().uniform_4i(location, x, y, z, w);
2402    }
2403    pub fn uniform_4iv(&self, location: GLint, values: I32VecRef) {
2404        self.get().uniform_4iv(location, values.as_slice());
2405    }
2406    pub fn uniform_4ui(&self, location: GLint, x: GLuint, y: GLuint, z: GLuint, w: GLuint) {
2407        self.get().uniform_4ui(location, x, y, z, w);
2408    }
2409    pub fn uniform_4fv(&self, location: GLint, values: F32VecRef) {
2410        self.get().uniform_4fv(location, values.as_slice());
2411    }
2412    pub fn uniform_matrix_2fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2413        self.get()
2414            .uniform_matrix_2fv(location, transpose, value.as_slice());
2415    }
2416    pub fn uniform_matrix_3fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2417        self.get()
2418            .uniform_matrix_3fv(location, transpose, value.as_slice());
2419    }
2420    pub fn uniform_matrix_4fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
2421        self.get()
2422            .uniform_matrix_4fv(location, transpose, value.as_slice());
2423    }
2424    pub fn depth_mask(&self, flag: bool) {
2425        self.get().depth_mask(flag);
2426    }
2427    pub fn depth_range(&self, near: f64, far: f64) {
2428        self.get().depth_range(near, far);
2429    }
2430    #[must_use]
2431    pub fn get_active_attrib(&self, program: GLuint, index: GLuint) -> GetActiveAttribReturn {
2432        let r = self.get().get_active_attrib(program, index);
2433        GetActiveAttribReturn {
2434            _0: r.0,
2435            _1: r.1,
2436            _2: r.2.into(),
2437        }
2438    }
2439    #[must_use]
2440    pub fn get_active_uniform(&self, program: GLuint, index: GLuint) -> GetActiveUniformReturn {
2441        let r = self.get().get_active_uniform(program, index);
2442        GetActiveUniformReturn {
2443            _0: r.0,
2444            _1: r.1,
2445            _2: r.2.into(),
2446        }
2447    }
2448    #[must_use]
2449    pub fn get_active_uniforms_iv(
2450        &self,
2451        program: GLuint,
2452        indices: GLuintVec,
2453        pname: GLenum,
2454    ) -> GLintVec {
2455        self.get()
2456            .get_active_uniforms_iv(program, indices.into_library_owned_vec(), pname)
2457            .into()
2458    }
2459    #[must_use]
2460    pub fn get_active_uniform_block_i(
2461        &self,
2462        program: GLuint,
2463        index: GLuint,
2464        pname: GLenum,
2465    ) -> GLint {
2466        self.get().get_active_uniform_block_i(program, index, pname)
2467    }
2468    #[must_use]
2469    pub fn get_active_uniform_block_iv(
2470        &self,
2471        program: GLuint,
2472        index: GLuint,
2473        pname: GLenum,
2474    ) -> GLintVec {
2475        self.get()
2476            .get_active_uniform_block_iv(program, index, pname)
2477            .into()
2478    }
2479    #[must_use]
2480    pub fn get_active_uniform_block_name(&self, program: GLuint, index: GLuint) -> AzString {
2481        self.get()
2482            .get_active_uniform_block_name(program, index)
2483            .into()
2484    }
2485    #[must_use]
2486    pub fn get_attrib_location(&self, program: GLuint, name: &str) -> c_int {
2487        self.get().get_attrib_location(program, name)
2488    }
2489    #[must_use]
2490    pub fn get_frag_data_location(&self, program: GLuint, name: &str) -> c_int {
2491        self.get().get_frag_data_location(program, name)
2492    }
2493    #[must_use]
2494    pub fn get_uniform_location(&self, program: GLuint, name: &str) -> c_int {
2495        self.get().get_uniform_location(program, name)
2496    }
2497    #[must_use]
2498    pub fn get_program_info_log(&self, program: GLuint) -> AzString {
2499        self.get().get_program_info_log(program).into()
2500    }
2501    pub fn get_program_iv(&self, program: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2502        unsafe {
2503            self.get()
2504                .get_program_iv(program, pname, result.as_mut_slice());
2505        }
2506    }
2507    #[must_use]
2508    pub fn get_program_binary(&self, program: GLuint) -> GetProgramBinaryReturn {
2509        let r = self.get().get_program_binary(program);
2510        GetProgramBinaryReturn {
2511            _0: r.0.into(),
2512            _1: r.1,
2513        }
2514    }
2515    pub fn program_binary(&self, program: GLuint, format: GLenum, binary: U8VecRef) {
2516        self.get()
2517            .program_binary(program, format, binary.as_slice());
2518    }
2519    pub fn program_parameter_i(&self, program: GLuint, pname: GLenum, value: GLint) {
2520        self.get().program_parameter_i(program, pname, value);
2521    }
2522    pub fn get_vertex_attrib_iv(&self, index: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2523        unsafe {
2524            self.get()
2525                .get_vertex_attrib_iv(index, pname, result.as_mut_slice());
2526        }
2527    }
2528    pub fn get_vertex_attrib_fv(&self, index: GLuint, pname: GLenum, mut result: GLfloatVecRefMut) {
2529        unsafe {
2530            self.get()
2531                .get_vertex_attrib_fv(index, pname, result.as_mut_slice());
2532        }
2533    }
2534    #[must_use]
2535    pub fn get_vertex_attrib_pointer_v(&self, index: GLuint, pname: GLenum) -> GLsizeiptr {
2536        self.get().get_vertex_attrib_pointer_v(index, pname)
2537    }
2538    #[must_use]
2539    pub fn get_buffer_parameter_iv(&self, target: GLuint, pname: GLenum) -> GLint {
2540        self.get().get_buffer_parameter_iv(target, pname)
2541    }
2542    #[must_use]
2543    pub fn get_shader_info_log(&self, shader: GLuint) -> AzString {
2544        self.get().get_shader_info_log(shader).into()
2545    }
2546    #[must_use]
2547    pub fn get_string(&self, which: GLenum) -> AzString {
2548        self.get().get_string(which).into()
2549    }
2550    #[must_use]
2551    pub fn get_string_i(&self, which: GLenum, index: GLuint) -> AzString {
2552        self.get().get_string_i(which, index).into()
2553    }
2554    pub fn get_shader_iv(&self, shader: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
2555        unsafe {
2556            self.get()
2557                .get_shader_iv(shader, pname, result.as_mut_slice());
2558        }
2559    }
2560    #[must_use]
2561    pub fn get_shader_precision_format(
2562        &self,
2563        shader_type: GLuint,
2564        precision_type: GLuint,
2565    ) -> GlShaderPrecisionFormatReturn {
2566        let r = self
2567            .get()
2568            .get_shader_precision_format(shader_type, precision_type);
2569        GlShaderPrecisionFormatReturn {
2570            _0: r.0,
2571            _1: r.1,
2572            _2: r.2,
2573        }
2574    }
2575    pub fn compile_shader(&self, shader: GLuint) {
2576        self.get().compile_shader(shader);
2577    }
2578    #[must_use]
2579    pub fn create_program(&self) -> GLuint {
2580        self.get().create_program()
2581    }
2582    pub fn delete_program(&self, program: GLuint) {
2583        self.get().delete_program(program);
2584    }
2585    #[must_use]
2586    pub fn create_shader(&self, shader_type: GLenum) -> GLuint {
2587        self.get().create_shader(shader_type)
2588    }
2589    pub fn delete_shader(&self, shader: GLuint) {
2590        self.get().delete_shader(shader);
2591    }
2592    pub fn detach_shader(&self, program: GLuint, shader: GLuint) {
2593        self.get().detach_shader(program, shader);
2594    }
2595    pub fn link_program(&self, program: GLuint) {
2596        self.get().link_program(program);
2597    }
2598    pub fn clear_color(&self, r: f32, g: f32, b: f32, a: f32) {
2599        self.get().clear_color(r, g, b, a);
2600    }
2601    pub fn clear(&self, buffer_mask: GLbitfield) {
2602        self.get().clear(buffer_mask);
2603    }
2604    pub fn clear_depth(&self, depth: f64) {
2605        self.get().clear_depth(depth);
2606    }
2607    pub fn clear_stencil(&self, s: GLint) {
2608        self.get().clear_stencil(s);
2609    }
2610    pub fn flush(&self) {
2611        self.get().flush();
2612    }
2613    pub fn finish(&self) {
2614        self.get().finish();
2615    }
2616    #[must_use]
2617    pub fn get_error(&self) -> GLenum {
2618        self.get().get_error()
2619    }
2620    pub fn stencil_mask(&self, mask: GLuint) {
2621        self.get().stencil_mask(mask);
2622    }
2623    pub fn stencil_mask_separate(&self, face: GLenum, mask: GLuint) {
2624        self.get().stencil_mask_separate(face, mask);
2625    }
2626    pub fn stencil_func(&self, func: GLenum, ref_: GLint, mask: GLuint) {
2627        self.get().stencil_func(func, ref_, mask);
2628    }
2629    pub fn stencil_func_separate(&self, face: GLenum, func: GLenum, ref_: GLint, mask: GLuint) {
2630        self.get().stencil_func_separate(face, func, ref_, mask);
2631    }
2632    pub fn stencil_op(&self, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
2633        self.get().stencil_op(sfail, dpfail, dppass);
2634    }
2635    pub fn stencil_op_separate(&self, face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
2636        self.get().stencil_op_separate(face, sfail, dpfail, dppass);
2637    }
2638    pub fn egl_image_target_texture2d_oes(&self, target: GLenum, image: GlVoidPtrConst) {
2639        self.get()
2640            .egl_image_target_texture2d_oes(target, image.ptr as *const c_void);
2641    }
2642    pub fn generate_mipmap(&self, target: GLenum) {
2643        self.get().generate_mipmap(target);
2644    }
2645    pub fn insert_event_marker_ext(&self, message: &str) {
2646        self.get().insert_event_marker_ext(message);
2647    }
2648    pub fn push_group_marker_ext(&self, message: &str) {
2649        self.get().push_group_marker_ext(message);
2650    }
2651    pub fn pop_group_marker_ext(&self) {
2652        self.get().pop_group_marker_ext();
2653    }
2654    pub fn debug_message_insert_khr(
2655        &self,
2656        source: GLenum,
2657        type_: GLenum,
2658        id: GLuint,
2659        severity: GLenum,
2660        message: &str,
2661    ) {
2662        self.get()
2663            .debug_message_insert_khr(source, type_, id, severity, message);
2664    }
2665    pub fn push_debug_group_khr(&self, source: GLenum, id: GLuint, message: &str) {
2666        self.get().push_debug_group_khr(source, id, message);
2667    }
2668    pub fn pop_debug_group_khr(&self) {
2669        self.get().pop_debug_group_khr();
2670    }
2671    #[must_use]
2672    pub fn fence_sync(&self, condition: GLenum, flags: GLbitfield) -> GLsyncPtr {
2673        GLsyncPtr::new(self.get().fence_sync(condition, flags))
2674    }
2675    #[must_use]
2676    pub fn client_wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) -> u32 {
2677        self.get().client_wait_sync(sync.get(), flags, timeout)
2678    }
2679    pub fn wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) {
2680        self.get().wait_sync(sync.get(), flags, timeout);
2681    }
2682    pub fn delete_sync(&self, sync: GLsyncPtr) {
2683        self.get().delete_sync(sync.get());
2684    }
2685    pub fn texture_range_apple(&self, target: GLenum, data: U8VecRef) {
2686        self.get().texture_range_apple(target, data.as_slice());
2687    }
2688    #[must_use]
2689    pub fn gen_fences_apple(&self, n: GLsizei) -> GLuintVec {
2690        self.get().gen_fences_apple(n).into()
2691    }
2692    pub fn delete_fences_apple(&self, fences: GLuintVecRef) {
2693        self.get().delete_fences_apple(fences.as_slice());
2694    }
2695    pub fn set_fence_apple(&self, fence: GLuint) {
2696        self.get().set_fence_apple(fence);
2697    }
2698    pub fn finish_fence_apple(&self, fence: GLuint) {
2699        self.get().finish_fence_apple(fence);
2700    }
2701    pub fn test_fence_apple(&self, fence: GLuint) {
2702        self.get().test_fence_apple(fence);
2703    }
2704    #[must_use]
2705    pub fn test_object_apple(&self, object: GLenum, name: GLuint) -> GLboolean {
2706        self.get().test_object_apple(object, name)
2707    }
2708    pub fn finish_object_apple(&self, object: GLenum, name: GLuint) {
2709        self.get().finish_object_apple(object, name);
2710    }
2711    #[must_use]
2712    pub fn get_frag_data_index(&self, program: GLuint, name: &str) -> GLint {
2713        self.get().get_frag_data_index(program, name)
2714    }
2715    pub fn blend_barrier_khr(&self) {
2716        self.get().blend_barrier_khr();
2717    }
2718    pub fn bind_frag_data_location_indexed(
2719        &self,
2720        program: GLuint,
2721        color_number: GLuint,
2722        index: GLuint,
2723        name: &str,
2724    ) {
2725        self.get()
2726            .bind_frag_data_location_indexed(program, color_number, index, name);
2727    }
2728    #[must_use]
2729    pub fn get_debug_messages(&self) -> DebugMessageVec {
2730        let dmv: Vec<DebugMessage> = self
2731            .get()
2732            .get_debug_messages()
2733            .into_iter()
2734            .map(|d| DebugMessage {
2735                message: d.message.into(),
2736                source: d.source,
2737                ty: d.ty,
2738                id: d.id,
2739                severity: d.severity,
2740            })
2741            .collect();
2742        dmv.into()
2743    }
2744    pub fn provoking_vertex_angle(&self, mode: GLenum) {
2745        self.get().provoking_vertex_angle(mode);
2746    }
2747    #[must_use]
2748    pub fn gen_vertex_arrays_apple(&self, n: GLsizei) -> GLuintVec {
2749        self.get().gen_vertex_arrays_apple(n).into()
2750    }
2751    pub fn bind_vertex_array_apple(&self, vao: GLuint) {
2752        self.get().bind_vertex_array_apple(vao);
2753    }
2754    pub fn delete_vertex_arrays_apple(&self, vertex_arrays: GLuintVecRef) {
2755        self.get()
2756            .delete_vertex_arrays_apple(vertex_arrays.as_slice());
2757    }
2758    pub fn copy_texture_chromium(
2759        &self,
2760        source_id: GLuint,
2761        source_level: GLint,
2762        dest_target: GLenum,
2763        dest_id: GLuint,
2764        dest_level: GLint,
2765        internal_format: GLint,
2766        dest_type: GLenum,
2767        unpack_flip_y: GLboolean,
2768        unpack_premultiply_alpha: GLboolean,
2769        unpack_unmultiply_alpha: GLboolean,
2770    ) {
2771        self.get().copy_texture_chromium(
2772            source_id,
2773            source_level,
2774            dest_target,
2775            dest_id,
2776            dest_level,
2777            internal_format,
2778            dest_type,
2779            unpack_flip_y,
2780            unpack_premultiply_alpha,
2781            unpack_unmultiply_alpha,
2782        );
2783    }
2784    pub fn copy_sub_texture_chromium(
2785        &self,
2786        source_id: GLuint,
2787        source_level: GLint,
2788        dest_target: GLenum,
2789        dest_id: GLuint,
2790        dest_level: GLint,
2791        x_offset: GLint,
2792        y_offset: GLint,
2793        x: GLint,
2794        y: GLint,
2795        width: GLsizei,
2796        height: GLsizei,
2797        unpack_flip_y: GLboolean,
2798        unpack_premultiply_alpha: GLboolean,
2799        unpack_unmultiply_alpha: GLboolean,
2800    ) {
2801        self.get().copy_sub_texture_chromium(
2802            source_id,
2803            source_level,
2804            dest_target,
2805            dest_id,
2806            dest_level,
2807            x_offset,
2808            y_offset,
2809            x,
2810            y,
2811            width,
2812            height,
2813            unpack_flip_y,
2814            unpack_premultiply_alpha,
2815            unpack_unmultiply_alpha,
2816        );
2817    }
2818    pub fn egl_image_target_renderbuffer_storage_oes(&self, target: u32, image: GlVoidPtrConst) {
2819        self.get()
2820            .egl_image_target_renderbuffer_storage_oes(target, image.ptr as *const c_void);
2821    }
2822    pub fn copy_texture_3d_angle(
2823        &self,
2824        source_id: GLuint,
2825        source_level: GLint,
2826        dest_target: GLenum,
2827        dest_id: GLuint,
2828        dest_level: GLint,
2829        internal_format: GLint,
2830        dest_type: GLenum,
2831        unpack_flip_y: GLboolean,
2832        unpack_premultiply_alpha: GLboolean,
2833        unpack_unmultiply_alpha: GLboolean,
2834    ) {
2835        self.get().copy_texture_3d_angle(
2836            source_id,
2837            source_level,
2838            dest_target,
2839            dest_id,
2840            dest_level,
2841            internal_format,
2842            dest_type,
2843            unpack_flip_y,
2844            unpack_premultiply_alpha,
2845            unpack_unmultiply_alpha,
2846        );
2847    }
2848    pub fn copy_sub_texture_3d_angle(
2849        &self,
2850        source_id: GLuint,
2851        source_level: GLint,
2852        dest_target: GLenum,
2853        dest_id: GLuint,
2854        dest_level: GLint,
2855        x_offset: GLint,
2856        y_offset: GLint,
2857        z_offset: GLint,
2858        x: GLint,
2859        y: GLint,
2860        z: GLint,
2861        width: GLsizei,
2862        height: GLsizei,
2863        depth: GLsizei,
2864        unpack_flip_y: GLboolean,
2865        unpack_premultiply_alpha: GLboolean,
2866        unpack_unmultiply_alpha: GLboolean,
2867    ) {
2868        self.get().copy_sub_texture_3d_angle(
2869            source_id,
2870            source_level,
2871            dest_target,
2872            dest_id,
2873            dest_level,
2874            x_offset,
2875            y_offset,
2876            z_offset,
2877            x,
2878            y,
2879            z,
2880            width,
2881            height,
2882            depth,
2883            unpack_flip_y,
2884            unpack_premultiply_alpha,
2885            unpack_unmultiply_alpha,
2886        );
2887    }
2888    pub fn buffer_storage(
2889        &self,
2890        target: GLenum,
2891        size: GLsizeiptr,
2892        data: GlVoidPtrConst,
2893        flags: GLbitfield,
2894    ) {
2895        self.get().buffer_storage(target, size, data.ptr, flags);
2896    }
2897    pub fn flush_mapped_buffer_range(&self, target: GLenum, offset: GLintptr, length: GLsizeiptr) {
2898        self.get().flush_mapped_buffer_range(target, offset, length);
2899    }
2900}
2901
2902impl PartialEq for GlContextPtr {
2903    fn eq(&self, rhs: &Self) -> bool {
2904        self.as_usize().eq(&rhs.as_usize())
2905    }
2906}
2907
2908impl Eq for GlContextPtr {}
2909
2910impl PartialOrd for GlContextPtr {
2911    fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
2912        self.as_usize().partial_cmp(&rhs.as_usize())
2913    }
2914}
2915
2916impl Ord for GlContextPtr {
2917    fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
2918        self.as_usize().cmp(&rhs.as_usize())
2919    }
2920}
2921
2922/// Saved OpenGL state for save/restore around framebuffer operations.
2923/// Used by `Texture::clear()` and `GlShader::draw()` to avoid corrupting
2924/// the caller's GL state.
2925// the `current_` prefix is intentional: each field holds the saved CURRENT GL
2926// binding captured at save() to be restored in restore().
2927#[allow(clippy::struct_field_names)]
2928struct GlStateSave {
2929    current_multisample: [u8; 1],
2930    current_index_buffer: [i32; 1],
2931    current_vertex_buffer: [i32; 1],
2932    current_vertex_array_object: [i32; 1],
2933    current_program: [i32; 1],
2934    current_framebuffers: [i32; 1],
2935    current_renderbuffers: [i32; 1],
2936    current_texture_2d: [i32; 1],
2937}
2938
2939impl GlStateSave {
2940    fn save(gl_context: &GlContextPtr) -> Self {
2941        let mut s = Self {
2942            current_multisample: [0],
2943            current_index_buffer: [0],
2944            current_vertex_buffer: [0],
2945            current_vertex_array_object: [0],
2946            current_program: [0],
2947            current_framebuffers: [0],
2948            current_renderbuffers: [0],
2949            current_texture_2d: [0],
2950        };
2951
2952        gl_context.get_boolean_v(gl::MULTISAMPLE, (&mut s.current_multisample[..]).into());
2953        gl_context.get_integer_v(
2954            gl::ARRAY_BUFFER_BINDING,
2955            (&mut s.current_vertex_buffer[..]).into(),
2956        );
2957        gl_context.get_integer_v(
2958            gl::ELEMENT_ARRAY_BUFFER_BINDING,
2959            (&mut s.current_index_buffer[..]).into(),
2960        );
2961        gl_context.get_integer_v(gl::CURRENT_PROGRAM, (&mut s.current_program[..]).into());
2962        gl_context.get_integer_v(
2963            gl::VERTEX_ARRAY_BINDING,
2964            (&mut s.current_vertex_array_object[..]).into(),
2965        );
2966        gl_context.get_integer_v(gl::RENDERBUFFER, (&mut s.current_renderbuffers[..]).into());
2967        gl_context.get_integer_v(gl::FRAMEBUFFER, (&mut s.current_framebuffers[..]).into());
2968        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut s.current_texture_2d[..]).into());
2969
2970        s
2971    }
2972
2973    // OpenGL binding: state values passed to the gl API as GLuint/GLsizei.
2974    #[allow(clippy::cast_sign_loss)]
2975    fn restore(&self, gl_context: &GlContextPtr) {
2976        if u32::from(self.current_multisample[0]) == gl::TRUE {
2977            gl_context.enable(gl::MULTISAMPLE);
2978        }
2979        gl_context.bind_framebuffer(gl::FRAMEBUFFER, self.current_framebuffers[0] as u32);
2980        gl_context.bind_texture(gl::TEXTURE_2D, self.current_texture_2d[0] as u32);
2981        gl_context.bind_buffer(gl::RENDERBUFFER, self.current_renderbuffers[0] as u32);
2982        gl_context.bind_vertex_array(self.current_vertex_array_object[0] as u32);
2983        gl_context.bind_buffer(
2984            gl::ELEMENT_ARRAY_BUFFER,
2985            self.current_index_buffer[0] as u32,
2986        );
2987        gl_context.bind_buffer(gl::ARRAY_BUFFER, self.current_vertex_buffer[0] as u32);
2988        gl_context.use_program(self.current_program[0] as u32);
2989    }
2990}
2991
2992/// AUDIT: RAII guard that deletes a transient framebuffer + renderbuffer on
2993/// scope exit. Used by `Texture::clear` and `GlShader::draw` so that a panic
2994/// mid-path (e.g. an `.unwrap()` on an empty gen-list, or any GL step that
2995/// panics) can't leak the FBO/RBO. Ids of `0` are skipped (GL treats delete-0
2996/// as a no-op anyway, but this keeps intent explicit).
2997struct FboRboGuard<'a> {
2998    gl_context: &'a GlContextPtr,
2999    framebuffer_id: GLuint,
3000    renderbuffer_id: GLuint,
3001}
3002
3003impl Drop for FboRboGuard<'_> {
3004    fn drop(&mut self) {
3005        if self.framebuffer_id != 0 {
3006            self.gl_context
3007                .delete_framebuffers((&[self.framebuffer_id])[..].into());
3008        }
3009        if self.renderbuffer_id != 0 {
3010            self.gl_context
3011                .delete_renderbuffers((&[self.renderbuffer_id])[..].into());
3012        }
3013    }
3014}
3015
3016/// OpenGL texture, use `ReadOnlyWindow::create_texture` to create a texture
3017#[repr(C)]
3018pub struct Texture {
3019    /// A reference-counted pointer to the OpenGL context (so that the texture can be deleted in
3020    /// the destructor)
3021    pub gl_context: GlContextPtr,
3022    /// Raw OpenGL texture ID
3023    pub texture_id: GLuint,
3024    /// Reference count, shared across
3025    pub refcount: *const AtomicUsize,
3026    /// Size of this texture (in pixels)
3027    pub size: PhysicalSizeU32,
3028    /// Format of the texture (rgba8, brga8, etc.)
3029    pub format: RawImageFormat,
3030    /// Background color of this texture
3031    pub background_color: ColorU,
3032    /// Hints and flags for optimization purposes
3033    pub flags: TextureFlags,
3034    pub run_destructor: bool,
3035}
3036
3037impl Clone for Texture {
3038    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3039    fn clone(&self) -> Self {
3040        unsafe {
3041            (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst);
3042        }
3043        Self {
3044            gl_context: self.gl_context.clone(),
3045            texture_id: self.texture_id,
3046            refcount: self.refcount,
3047            size: self.size,
3048            format: self.format,
3049            background_color: self.background_color,
3050            flags: self.flags,
3051            run_destructor: true,
3052        }
3053    }
3054}
3055
3056impl_option!(
3057    Texture,
3058    OptionTexture,
3059    copy = false,
3060    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3061);
3062
3063impl Texture {
3064    #[must_use]
3065    pub fn create(
3066        texture_id: GLuint,
3067        flags: TextureFlags,
3068        size: PhysicalSizeU32,
3069        background_color: ColorU,
3070        gl_context: GlContextPtr,
3071        format: RawImageFormat,
3072    ) -> Self {
3073        Self {
3074            texture_id,
3075            flags,
3076            size,
3077            background_color,
3078            gl_context,
3079            format,
3080            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3081            run_destructor: true,
3082        }
3083    }
3084
3085    // OpenGL binding: gl::* enum constants and texture dimensions are passed as
3086    // GLint/GLsizei (i32); the values are GL-bounded and the `as i32` casts are the
3087    // idiomatic form for the gl API.
3088    #[allow(clippy::cast_possible_wrap)]
3089    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
3090    #[must_use]
3091    pub fn allocate_rgba8(
3092        gl_context: GlContextPtr,
3093        size: PhysicalSizeU32,
3094        background: ColorU,
3095    ) -> Self {
3096        let textures = gl_context.gen_textures(1);
3097        let texture_id = textures.as_ref()[0];
3098
3099        let mut current_texture_2d = [0_i32];
3100        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut current_texture_2d[..]).into());
3101
3102        gl_context.bind_texture(gl::TEXTURE_2D, texture_id);
3103        gl_context.tex_image_2d(
3104            gl::TEXTURE_2D,
3105            0,
3106            gl::RGBA as i32,
3107            size.width as i32,
3108            size.height as i32,
3109            0,
3110            gl::RGBA,
3111            gl::UNSIGNED_BYTE,
3112            None.into(),
3113        );
3114        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
3115        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
3116        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
3117        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
3118        gl_context.bind_texture(gl::TEXTURE_2D, current_texture_2d[0] as u32);
3119
3120        Self::create(
3121            texture_id,
3122            TextureFlags {
3123                is_opaque: false,
3124                is_video_texture: false,
3125            },
3126            size,
3127            background,
3128            gl_context,
3129            // Format is BGRA8 for WebRender integration, despite the GL upload using RGBA
3130            RawImageFormat::BGRA8,
3131        )
3132    }
3133
3134    /// # Panics
3135    ///
3136    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
3137    // OpenGL binding: gl::* enum constants and texture dimensions passed as
3138    // GLint/GLsizei (i32); values are GL-bounded, `as i32` is the idiomatic form.
3139    #[allow(clippy::cast_possible_wrap)]
3140    pub fn clear(&mut self) {
3141        let saved = GlStateSave::save(&self.gl_context);
3142
3143        let framebuffers = self.gl_context.gen_framebuffers(1);
3144        let framebuffer_id = *framebuffers.get(0).unwrap();
3145        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
3146        // panic in `gen_renderbuffers().get(0).unwrap()` can't leak it.
3147        let mut fbo_rbo_guard = FboRboGuard {
3148            gl_context: &self.gl_context,
3149            framebuffer_id,
3150            renderbuffer_id: 0,
3151        };
3152        self.gl_context
3153            .bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
3154
3155        let depthbuffers = self.gl_context.gen_renderbuffers(1);
3156        let depthbuffer_id = *depthbuffers.get(0).unwrap();
3157        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
3158
3159        self.gl_context
3160            .bind_texture(gl::TEXTURE_2D, self.texture_id);
3161        self.gl_context.tex_image_2d(
3162            gl::TEXTURE_2D,
3163            0,
3164            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
3165            self.size.width as i32,
3166            self.size.height as i32,
3167            0,
3168            gl::RGBA, // gl::BGRA?
3169            gl::UNSIGNED_BYTE,
3170            None.into(),
3171        );
3172        self.gl_context
3173            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
3174        self.gl_context
3175            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
3176        self.gl_context.tex_parameter_i(
3177            gl::TEXTURE_2D,
3178            gl::TEXTURE_WRAP_S,
3179            gl::CLAMP_TO_EDGE as i32,
3180        );
3181        self.gl_context.tex_parameter_i(
3182            gl::TEXTURE_2D,
3183            gl::TEXTURE_WRAP_T,
3184            gl::CLAMP_TO_EDGE as i32,
3185        );
3186
3187        self.gl_context
3188            .bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
3189        self.gl_context.renderbuffer_storage(
3190            gl::RENDERBUFFER,
3191            gl::DEPTH_COMPONENT,
3192            self.size.width as i32,
3193            self.size.height as i32,
3194        );
3195        self.gl_context.framebuffer_renderbuffer(
3196            gl::FRAMEBUFFER,
3197            gl::DEPTH_ATTACHMENT,
3198            gl::RENDERBUFFER,
3199            depthbuffer_id,
3200        );
3201
3202        self.gl_context.framebuffer_texture_2d(
3203            gl::FRAMEBUFFER,
3204            gl::COLOR_ATTACHMENT0,
3205            gl::TEXTURE_2D,
3206            self.texture_id,
3207            0,
3208        );
3209        self.gl_context
3210            .draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
3211
3212        let clear_color: ColorF = self.background_color.into();
3213        self.gl_context
3214            .clear_color(clear_color.r, clear_color.g, clear_color.b, clear_color.a);
3215        self.gl_context.clear_depth(0.0);
3216        self.gl_context
3217            .clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
3218
3219        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
3220        // also runs on an unwinding panic), so we restore state and let the
3221        // guard reclaim the GL objects at scope exit.
3222        saved.restore(&self.gl_context);
3223        drop(fbo_rbo_guard);
3224    }
3225
3226    #[must_use]
3227    pub fn get_descriptor(&self) -> ImageDescriptor {
3228        ImageDescriptor {
3229            format: self.format,
3230            width: self.size.width as usize,
3231            height: self.size.height as usize,
3232            stride: None.into(),
3233            offset: 0,
3234            flags: ImageDescriptorFlags {
3235                is_opaque: self.flags.is_opaque,
3236                // The texture gets mapped 1:1 onto the display, so there is no need for mipmaps
3237                allow_mipmaps: false,
3238            },
3239        }
3240    }
3241
3242    /// Draws a `TessellatedGPUSvgNode` with the given color to the texture
3243    pub fn draw_tesselated_svg_gpu_node(
3244        &mut self,
3245        node: &TessellatedGPUSvgNode,
3246        size: PhysicalSizeU32,
3247        color: ColorU,
3248        transforms: StyleTransformVec,
3249    ) -> bool {
3250        node.draw(self, size, color, transforms)
3251    }
3252
3253    /// Draws a `TessellatedColoredGPUSvgNode` to the texture
3254    pub fn draw_tesselated_colored_svg_gpu_node(
3255        &mut self,
3256        node: &crate::svg::TessellatedColoredGPUSvgNode,
3257        size: PhysicalSizeU32,
3258        transforms: StyleTransformVec,
3259    ) -> bool {
3260        node.draw(self, size, transforms)
3261    }
3262}
3263
3264#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3265#[repr(C)]
3266pub struct TextureFlags {
3267    /// Whether this texture contains an alpha component
3268    pub is_opaque: bool,
3269    /// Optimization: use the compositor instead of OpenGL for energy optimization
3270    pub is_video_texture: bool,
3271}
3272
3273impl ::core::fmt::Display for Texture {
3274    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3275        write!(
3276            f,
3277            "Texture {{ id: {}, {}x{} }}",
3278            self.texture_id, self.size.width, self.size.height
3279        )
3280    }
3281}
3282
3283macro_rules! impl_traits_for_gl_object {
3284    ($struct_name:ident, $gl_id_field:ident) => {
3285        impl ::core::fmt::Debug for $struct_name {
3286            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3287                write!(f, "{}", self)
3288            }
3289        }
3290
3291        impl Hash for $struct_name {
3292            fn hash<H: Hasher>(&self, state: &mut H) {
3293                self.$gl_id_field.hash(state);
3294            }
3295        }
3296
3297        impl PartialEq for $struct_name {
3298            fn eq(&self, other: &$struct_name) -> bool {
3299                self.$gl_id_field == other.$gl_id_field
3300            }
3301        }
3302
3303        impl Eq for $struct_name {}
3304
3305        impl PartialOrd for $struct_name {
3306            fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
3307                Some((self.$gl_id_field).cmp(&(other.$gl_id_field)))
3308            }
3309        }
3310
3311        impl Ord for $struct_name {
3312            fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
3313                (self.$gl_id_field).cmp(&(other.$gl_id_field))
3314            }
3315        }
3316    };
3317}
3318
3319impl Texture {
3320    /// GPU painting: stamp one soft-brush dab centered at (`cx`, `cy`) in texture
3321    /// pixel coordinates (origin top-left, matching [`RawImage::paint_dot`]).
3322    /// No-op if the GL context is unusable -- the caller should then use the CPU
3323    /// `RawImage` path (`GlContextPtr::is_gl_usable`).
3324    pub fn paint_dot(&mut self, cx: f32, cy: f32, brush: Brush) {
3325        self.paint_stroke(cx, cy, cx, cy, brush);
3326    }
3327
3328    /// GPU painting: stamp dabs along (`x0`,`y0`)->(`x1`,`y1`) into this texture
3329    /// via an FBO + the soft-brush shader, alpha-over blended. Same spacing +
3330    /// falloff as the CPU `RawImage::paint_stroke`. No-op if GL is unusable.
3331    #[allow(clippy::suboptimal_flops)] // mul_add not guaranteed faster/available without target +fma; keep explicit a*b+c
3332    #[allow(
3333        clippy::cast_possible_truncation,
3334        clippy::cast_possible_wrap,
3335        clippy::cast_precision_loss,
3336        clippy::cast_sign_loss
3337    )] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3338    #[allow(clippy::many_single_char_names)] // domain-standard colour/coordinate component names
3339    pub fn paint_stroke(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, brush: Brush) {
3340        let gl = self.gl_context.clone();
3341        let prog = gl.get_brush_shader();
3342        let (tw, th) = (self.size.width as f32, self.size.height as f32);
3343        // `!(radius > 0.0)` intentionally also rejects NaN (`radius <= 0.0` would not).
3344        #[allow(clippy::neg_cmp_op_on_partial_ord)]
3345        if prog == 0 || self.texture_id == 0 || !(brush.radius > 0.0) || tw <= 0.0 || th <= 0.0 {
3346            return;
3347        }
3348
3349        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
3350        let vbo = gl.gen_buffers(1).get(0).copied().unwrap_or(0);
3351        if fbo == 0 || vbo == 0 {
3352            if fbo != 0 {
3353                gl.delete_framebuffers((&[fbo][..]).into());
3354            }
3355            if vbo != 0 {
3356                gl.delete_buffers((&[vbo][..]).into());
3357            }
3358            return;
3359        }
3360
3361        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
3362        gl.framebuffer_texture_2d(
3363            gl::FRAMEBUFFER,
3364            gl::COLOR_ATTACHMENT0,
3365            gl::TEXTURE_2D,
3366            self.texture_id,
3367            0,
3368        );
3369        gl.viewport(0, 0, self.size.width as i32, self.size.height as i32);
3370        gl.enable(gl::BLEND);
3371        gl.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
3372        gl.use_program(prog);
3373
3374        let a = (f32::from(brush.color.a) / 255.0) * brush.flow.clamp(0.0, 1.0);
3375        gl.uniform_4f(
3376            gl.get_uniform_location(prog, "uColor"),
3377            f32::from(brush.color.r) / 255.0,
3378            f32::from(brush.color.g) / 255.0,
3379            f32::from(brush.color.b) / 255.0,
3380            a,
3381        );
3382        gl.uniform_1f(gl.get_uniform_location(prog, "uHardness"), brush.hardness);
3383
3384        gl.bind_buffer(gl::ARRAY_BUFFER, vbo);
3385        gl.enable_vertex_attrib_array(0);
3386        gl.enable_vertex_attrib_array(1);
3387        gl.vertex_attrib_pointer_f32(0, 2, false, 16, 0);
3388        gl.vertex_attrib_pointer_f32(1, 2, false, 16, 8);
3389
3390        let dx = x1 - x0;
3391        let dy = y1 - y0;
3392        let len = dx.hypot(dy);
3393        let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
3394        let n = ((len / step).floor() as i32).max(0);
3395        let r = brush.radius;
3396        for i in 0..=n {
3397            let t = if n == 0 { 1.0 } else { i as f32 / n as f32 };
3398            let px = x0 + dx * t;
3399            let py = y0 + dy * t;
3400            // dab bbox -> NDC; y is flipped so (0,0) is top-left like the CPU path.
3401            let nx = |x: f32| (x / tw) * 2.0 - 1.0;
3402            let ny = |y: f32| 1.0 - (y / th) * 2.0;
3403            let (l, rr, tp, bt) = (nx(px - r), nx(px + r), ny(py - r), ny(py + r));
3404            // TRIANGLE_STRIP: TL, BL, TR, BR -- interleaved (pos.x, pos.y, uv.x, uv.y).
3405            let verts: [f32; 16] = [
3406                l, tp, -1.0, -1.0, l, bt, -1.0, 1.0, rr, tp, 1.0, -1.0, rr, bt, 1.0, 1.0,
3407            ];
3408            gl.buffer_data_untyped(
3409                gl::ARRAY_BUFFER,
3410                (verts.len() * size_of::<f32>()) as isize,
3411                GlVoidPtrConst {
3412                    ptr: verts.as_ptr() as *const GLvoid,
3413                    run_destructor: false,
3414                },
3415                gl::STREAM_DRAW,
3416            );
3417            gl.draw_arrays(gl::TRIANGLE_STRIP, 0, 4);
3418        }
3419
3420        gl.disable_vertex_attrib_array(0);
3421        gl.disable_vertex_attrib_array(1);
3422        gl.bind_buffer(gl::ARRAY_BUFFER, 0);
3423        gl.disable(gl::BLEND);
3424        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
3425        gl.delete_buffers((&[vbo][..]).into());
3426        gl.delete_framebuffers((&[fbo][..]).into());
3427    }
3428
3429    /// Read this texture's pixels back into an RGBA8 `RawImage` (top-left origin)
3430    /// -- for exporting the painted canvas to disk. Binds an FBO + glReadPixels.
3431    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
3432    #[must_use]
3433    pub fn copy_to_raw_image(&self) -> RawImage {
3434        let gl = self.gl_context.clone();
3435        let (w, h) = (self.size.width as i32, self.size.height as i32);
3436        if self.texture_id == 0 || w <= 0 || h <= 0 {
3437            return RawImage::null_image();
3438        }
3439        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
3440        if fbo == 0 {
3441            return RawImage::null_image();
3442        }
3443        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
3444        gl.framebuffer_texture_2d(
3445            gl::FRAMEBUFFER,
3446            gl::COLOR_ATTACHMENT0,
3447            gl::TEXTURE_2D,
3448            self.texture_id,
3449            0,
3450        );
3451        let pixels = gl.read_pixels(0, 0, w, h, gl::RGBA, gl::UNSIGNED_BYTE);
3452        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
3453        gl.delete_framebuffers((&[fbo][..]).into());
3454
3455        // glReadPixels uses a bottom-left origin; flip rows to top-left for saving.
3456        let mut bytes = pixels.into_library_owned_vec();
3457        let row = (w as usize) * 4;
3458        let hh = h as usize;
3459        if row > 0 && bytes.len() >= row * hh {
3460            for y in 0..hh / 2 {
3461                let yi = y * row;
3462                let yo = (hh - 1 - y) * row;
3463                for k in 0..row {
3464                    bytes.swap(yi + k, yo + k);
3465                }
3466            }
3467        }
3468        RawImage {
3469            pixels: RawImageData::U8(bytes.into()),
3470            width: w as usize,
3471            height: h as usize,
3472            premultiplied_alpha: true,
3473            data_format: RawImageFormat::RGBA8,
3474            tag: Vec::new().into(),
3475        }
3476    }
3477}
3478
3479impl_traits_for_gl_object!(Texture, texture_id);
3480
3481impl Drop for Texture {
3482    fn drop(&mut self) {
3483        // AUDIT: mirror `GlContextPtr::drop`. Without this guard a C-ABI
3484        // double-drop (drop_in_place run twice on the same byte-copied struct)
3485        // does a second `fetch_sub` on the already-freed refcount box (UAF) and
3486        // a second `delete_textures`. The first drop clears `run_destructor`, so
3487        // the second is a no-op.
3488        if !self.run_destructor {
3489            return;
3490        }
3491        self.run_destructor = false;
3492        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3493        if copies == 1 {
3494            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3495            self.gl_context
3496                .delete_textures((&[self.texture_id])[..].into());
3497        }
3498    }
3499}
3500
3501/// Describes the vertex layout and offsets
3502#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3503#[repr(C)]
3504pub struct VertexLayout {
3505    pub fields: VertexAttributeVec,
3506}
3507
3508impl_vec!(
3509    VertexAttribute,
3510    VertexAttributeVec,
3511    VertexAttributeVecDestructor,
3512    VertexAttributeVecDestructorType,
3513    VertexAttributeVecSlice,
3514    OptionVertexAttribute
3515);
3516impl_vec_debug!(VertexAttribute, VertexAttributeVec);
3517impl_vec_partialord!(VertexAttribute, VertexAttributeVec);
3518impl_vec_ord!(VertexAttribute, VertexAttributeVec);
3519impl_vec_clone!(
3520    VertexAttribute,
3521    VertexAttributeVec,
3522    VertexAttributeVecDestructor
3523);
3524impl_vec_partialeq!(VertexAttribute, VertexAttributeVec);
3525impl_vec_eq!(VertexAttribute, VertexAttributeVec);
3526impl_vec_hash!(VertexAttribute, VertexAttributeVec);
3527
3528impl VertexLayout {
3529    /// Submits the vertex buffer description to OpenGL
3530    // OpenGL binding: vertex-attribute layout (locations, item counts, strides,
3531    // offsets) passed to the gl API as GLuint/GLint/GLsizei; values are GL-bounded.
3532    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
3533    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3534    pub fn bind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
3535        const VERTICES_ARE_NORMALIZED: bool = false;
3536
3537        let mut offset = 0;
3538
3539        let stride_between_vertices: usize =
3540            self.fields.iter().map(VertexAttribute::get_stride).sum();
3541
3542        for vertex_attribute in &self.fields {
3543            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
3544                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
3545                |ll| *ll as i32,
3546            );
3547
3548            gl_context.vertex_attrib_pointer(
3549                attribute_location as u32,
3550                vertex_attribute.item_count as i32,
3551                vertex_attribute.attribute_type.get_gl_id(),
3552                VERTICES_ARE_NORMALIZED,
3553                stride_between_vertices as i32,
3554                offset as u32,
3555            );
3556            gl_context.enable_vertex_attrib_array(attribute_location as u32);
3557            offset += vertex_attribute.get_stride();
3558        }
3559    }
3560
3561    /// Unsets the vertex buffer description
3562    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3563    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts
3564    pub fn unbind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
3565        for vertex_attribute in &self.fields {
3566            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
3567                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
3568                |ll| *ll as i32,
3569            );
3570            gl_context.disable_vertex_attrib_array(attribute_location as u32);
3571        }
3572    }
3573}
3574
3575#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3576#[repr(C)]
3577pub struct VertexAttribute {
3578    /// Attribute name of the vertex attribute in the vertex shader, i.e. `"vAttrXY"`
3579    pub va_name: AzString,
3580    /// If the vertex shader has a specific location, (like `layout(location = 2) vAttrXY`),
3581    /// use this instead of the name to look up the uniform location.
3582    pub layout_location: OptionUsize,
3583    /// Type of items of this attribute (i.e. for a `FloatVec2`, would be
3584    /// `VertexAttributeType::Float`)
3585    pub attribute_type: VertexAttributeType,
3586    /// Number of items of this attribute (i.e. for a `FloatVec2`, would be `2` (= 2 consecutive
3587    /// f32 values))
3588    pub item_count: usize,
3589}
3590
3591impl_option!(
3592    VertexAttribute,
3593    OptionVertexAttribute,
3594    copy = false,
3595    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
3596);
3597
3598impl VertexAttribute {
3599    #[must_use]
3600    pub const fn get_stride(&self) -> usize {
3601        self.attribute_type.get_mem_size() * self.item_count
3602    }
3603}
3604
3605#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3606#[repr(C)]
3607pub enum VertexAttributeType {
3608    /// Vertex attribute has type `f32`
3609    Float,
3610    /// Vertex attribute has type `f64`
3611    Double,
3612    /// Vertex attribute has type `u8`
3613    UnsignedByte,
3614    /// Vertex attribute has type `u16`
3615    UnsignedShort,
3616    /// Vertex attribute has type `u32`
3617    UnsignedInt,
3618}
3619
3620impl VertexAttributeType {
3621    /// Returns the OpenGL id for the vertex attribute type, ex. `gl::UNSIGNED_BYTE` for
3622    /// `VertexAttributeType::UnsignedByte`.
3623    #[must_use]
3624    pub const fn get_gl_id(&self) -> GLuint {
3625        use self::VertexAttributeType::{Double, Float, UnsignedByte, UnsignedInt, UnsignedShort};
3626        match self {
3627            Float => gl::FLOAT,
3628            Double => gl::DOUBLE,
3629            UnsignedByte => gl::UNSIGNED_BYTE,
3630            UnsignedShort => gl::UNSIGNED_SHORT,
3631            UnsignedInt => gl::UNSIGNED_INT,
3632        }
3633    }
3634
3635    #[must_use]
3636    pub const fn get_mem_size(&self) -> usize {
3637        use core::mem;
3638
3639        use self::VertexAttributeType::{Double, Float, UnsignedByte, UnsignedInt, UnsignedShort};
3640        match self {
3641            Float => size_of::<f32>(),
3642            Double => size_of::<f64>(),
3643            UnsignedByte => size_of::<u8>(),
3644            UnsignedShort => size_of::<u16>(),
3645            UnsignedInt => size_of::<u32>(),
3646        }
3647    }
3648}
3649
3650pub trait VertexLayoutDescription {
3651    fn get_description() -> VertexLayout;
3652}
3653
3654#[derive(Debug, PartialEq, Eq, PartialOrd)]
3655#[repr(C)]
3656pub struct VertexArrayObject {
3657    pub vertex_layout: VertexLayout,
3658    pub vao_id: GLuint,
3659    pub gl_context: GlContextPtr,
3660    pub refcount: *const AtomicUsize,
3661    pub run_destructor: bool,
3662}
3663
3664impl VertexArrayObject {
3665    #[must_use]
3666    pub fn new(vertex_layout: VertexLayout, vao_id: GLuint, gl_context: GlContextPtr) -> Self {
3667        Self {
3668            vertex_layout,
3669            vao_id,
3670            gl_context,
3671            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3672            run_destructor: true,
3673        }
3674    }
3675}
3676
3677impl Clone for VertexArrayObject {
3678    fn clone(&self) -> Self {
3679        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
3680        Self {
3681            vertex_layout: self.vertex_layout.clone(),
3682            vao_id: self.vao_id,
3683            gl_context: self.gl_context.clone(),
3684            refcount: self.refcount,
3685            run_destructor: true,
3686        }
3687    }
3688}
3689
3690impl Drop for VertexArrayObject {
3691    fn drop(&mut self) {
3692        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
3693        // freeing the refcount box twice (use-after-free) + double delete.
3694        if !self.run_destructor {
3695            return;
3696        }
3697        self.run_destructor = false;
3698        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3699        if copies == 1 {
3700            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3701            self.gl_context
3702                .delete_vertex_arrays((&[self.vao_id])[..].into());
3703        }
3704    }
3705}
3706
3707#[repr(C)]
3708pub struct VertexBuffer {
3709    pub vao: VertexArrayObject,
3710    pub vertex_buffer_id: GLuint,
3711    pub vertex_buffer_len: usize,
3712    pub index_buffer_id: GLuint,
3713    pub index_buffer_len: usize,
3714    pub refcount: *const AtomicUsize,
3715    pub index_buffer_format: IndexBufferFormat,
3716    pub run_destructor: bool,
3717}
3718
3719impl fmt::Display for VertexBuffer {
3720    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3721        write!(
3722            f,
3723            "VertexBuffer {{ buffer: {} (length: {}) }}",
3724            self.vertex_buffer_id, self.vertex_buffer_len
3725        )
3726    }
3727}
3728
3729impl_traits_for_gl_object!(VertexBuffer, vertex_buffer_id);
3730
3731impl Clone for VertexBuffer {
3732    fn clone(&self) -> Self {
3733        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
3734        Self {
3735            vao: self.vao.clone(),
3736            vertex_buffer_id: self.vertex_buffer_id,
3737            vertex_buffer_len: self.vertex_buffer_len,
3738            index_buffer_id: self.index_buffer_id,
3739            index_buffer_len: self.index_buffer_len,
3740            refcount: self.refcount,
3741            index_buffer_format: self.index_buffer_format,
3742            run_destructor: true,
3743        }
3744    }
3745}
3746
3747impl Drop for VertexBuffer {
3748    fn drop(&mut self) {
3749        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
3750        // freeing the refcount box twice (use-after-free) + double delete.
3751        if !self.run_destructor {
3752            return;
3753        }
3754        self.run_destructor = false;
3755        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
3756        if copies == 1 {
3757            self.vao.vertex_layout = VertexLayout {
3758                fields: VertexAttributeVec::from_const_slice(&[]),
3759            };
3760            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3761            self.vao
3762                .gl_context
3763                .delete_buffers((&[self.vertex_buffer_id, self.index_buffer_id])[..].into());
3764        }
3765    }
3766}
3767
3768impl VertexBuffer {
3769    /// # Panics
3770    ///
3771    /// Panics if the GL driver failed to create the vertex-array/buffer objects
3772    /// (the returned id lists are empty).
3773    // OpenGL binding: buffer sizes / vertex counts passed to the gl API as
3774    // GLsizeiptr/GLint; values are GL-bounded.
3775    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
3776    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
3777    pub fn new<T: VertexLayoutDescription>(
3778        gl_context: GlContextPtr,
3779        shader_program_id: GLuint,
3780        vertices: &[T],
3781        indices: &[u32],
3782        index_buffer_format: IndexBufferFormat,
3783    ) -> Self {
3784        use core::mem;
3785
3786        // Save the OpenGL state
3787        let mut current_vertex_array = [0_i32];
3788
3789        gl_context.get_integer_v(gl::VERTEX_ARRAY, (&mut current_vertex_array[..]).into());
3790
3791        let vertex_array_object = gl_context.gen_vertex_arrays(1);
3792        let vertex_array_object = vertex_array_object.get(0).unwrap();
3793
3794        let vertex_buffer_id = gl_context.gen_buffers(1);
3795        let vertex_buffer_id = vertex_buffer_id.get(0).unwrap();
3796
3797        let index_buffer_id = gl_context.gen_buffers(1);
3798        let index_buffer_id = index_buffer_id.get(0).unwrap();
3799
3800        gl_context.bind_vertex_array(*vertex_array_object);
3801
3802        // Upload vertex data to GPU
3803        gl_context.bind_buffer(gl::ARRAY_BUFFER, *vertex_buffer_id);
3804        gl_context.buffer_data_untyped(
3805            gl::ARRAY_BUFFER,
3806            size_of_val(vertices) as isize,
3807            GlVoidPtrConst {
3808                ptr: vertices.as_ptr() as *const core::ffi::c_void,
3809                run_destructor: true,
3810            },
3811            gl::STATIC_DRAW,
3812        );
3813
3814        // Generate the index buffer + upload data
3815        gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, *index_buffer_id);
3816        gl_context.buffer_data_untyped(
3817            gl::ELEMENT_ARRAY_BUFFER,
3818            size_of_val(indices) as isize,
3819            GlVoidPtrConst {
3820                ptr: indices.as_ptr() as *const core::ffi::c_void,
3821                run_destructor: true,
3822            },
3823            gl::STATIC_DRAW,
3824        );
3825
3826        let vertex_description = T::get_description();
3827        vertex_description.bind(&gl_context.ptr.ptr, shader_program_id);
3828
3829        // Reset the OpenGL state
3830        gl_context.bind_vertex_array(current_vertex_array[0] as u32);
3831
3832        Self::new_raw(
3833            *vertex_buffer_id,
3834            vertices.len(),
3835            VertexArrayObject::new(vertex_description, *vertex_array_object, gl_context),
3836            *index_buffer_id,
3837            indices.len(),
3838            index_buffer_format,
3839        )
3840    }
3841
3842    #[must_use]
3843    pub fn new_raw(
3844        vertex_buffer_id: GLuint,
3845        vertex_buffer_len: usize,
3846        vao: VertexArrayObject,
3847        index_buffer_id: GLuint,
3848        index_buffer_len: usize,
3849        index_buffer_format: IndexBufferFormat,
3850    ) -> Self {
3851        Self {
3852            vertex_buffer_id,
3853            vertex_buffer_len,
3854            vao,
3855            index_buffer_id,
3856            index_buffer_len,
3857            index_buffer_format,
3858            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3859            run_destructor: true,
3860        }
3861    }
3862}
3863
3864#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3865pub enum GlApiVersion {
3866    Gl { major: usize, minor: usize },
3867    GlEs { major: usize, minor: usize },
3868}
3869
3870impl GlApiVersion {
3871    /// Returns the OpenGL version of the context
3872    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
3873    #[must_use]
3874    pub fn get(gl_context: &GlContextPtr) -> Self {
3875        let mut major = [0];
3876        gl_context.get_integer_v(gl::MAJOR_VERSION, (&mut major[..]).into());
3877        let mut minor = [0];
3878        gl_context.get_integer_v(gl::MINOR_VERSION, (&mut minor[..]).into());
3879
3880        let major = major[0] as usize;
3881        let minor = minor[0] as usize;
3882
3883        match gl_context.get_type() {
3884            GlType::Gl => Self::Gl { major, minor },
3885            GlType::Gles => Self::GlEs { major, minor },
3886        }
3887    }
3888}
3889
3890#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3891#[repr(C)]
3892pub enum IndexBufferFormat {
3893    Points,
3894    Lines,
3895    LineStrip,
3896    Triangles,
3897    TriangleStrip,
3898    TriangleFan,
3899}
3900
3901impl IndexBufferFormat {
3902    /// Returns the `gl::TRIANGLE_STRIP` / `gl::POINTS`, etc.
3903    #[must_use]
3904    pub const fn get_gl_id(&self) -> GLuint {
3905        use self::IndexBufferFormat::{
3906            LineStrip, Lines, Points, TriangleFan, TriangleStrip, Triangles,
3907        };
3908        match self {
3909            Points => gl::POINTS,
3910            Lines => gl::LINES,
3911            LineStrip => gl::LINE_STRIP,
3912            Triangles => gl::TRIANGLES,
3913            TriangleStrip => gl::TRIANGLE_STRIP,
3914            TriangleFan => gl::TRIANGLE_FAN,
3915        }
3916    }
3917}
3918
3919#[derive(Debug, Clone, PartialEq, PartialOrd)]
3920#[repr(C)]
3921pub struct Uniform {
3922    pub uniform_name: AzString,
3923    pub uniform_type: UniformType,
3924}
3925
3926impl Uniform {
3927    pub fn create<S: Into<AzString>>(name: S, uniform_type: UniformType) -> Self {
3928        Self {
3929            uniform_name: name.into(),
3930            uniform_type,
3931        }
3932    }
3933}
3934
3935#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
3936#[repr(C, u8)]
3937pub enum UniformType {
3938    Float(f32),
3939    FloatVec2([f32; 2]),
3940    FloatVec3([f32; 3]),
3941    FloatVec4([f32; 4]),
3942    Int(i32),
3943    IntVec2([i32; 2]),
3944    IntVec3([i32; 3]),
3945    IntVec4([i32; 4]),
3946    UnsignedInt(u32),
3947    UnsignedIntVec2([u32; 2]),
3948    UnsignedIntVec3([u32; 3]),
3949    UnsignedIntVec4([u32; 4]),
3950    Matrix2 {
3951        transpose: bool,
3952        matrix: [f32; 2 * 2],
3953    },
3954    Matrix3 {
3955        transpose: bool,
3956        matrix: [f32; 3 * 3],
3957    },
3958    Matrix4 {
3959        transpose: bool,
3960        matrix: [f32; 4 * 4],
3961    },
3962}
3963
3964impl UniformType {
3965    /// Set a specific uniform
3966    pub fn set(self, gl_context: &Rc<GenericGlContext>, location: GLint) {
3967        use self::UniformType::{
3968            Float, FloatVec2, FloatVec3, FloatVec4, Int, IntVec2, IntVec3, IntVec4, Matrix2,
3969            Matrix3, Matrix4, UnsignedInt, UnsignedIntVec2, UnsignedIntVec3, UnsignedIntVec4,
3970        };
3971        match self {
3972            Float(r) => gl_context.uniform_1f(location, r),
3973            FloatVec2([r, g]) => gl_context.uniform_2f(location, r, g),
3974            FloatVec3([r, g, b]) => gl_context.uniform_3f(location, r, g, b),
3975            FloatVec4([r, g, b, a]) => gl_context.uniform_4f(location, r, g, b, a),
3976            Int(r) => gl_context.uniform_1i(location, r),
3977            IntVec2([r, g]) => gl_context.uniform_2i(location, r, g),
3978            IntVec3([r, g, b]) => gl_context.uniform_3i(location, r, g, b),
3979            IntVec4([r, g, b, a]) => gl_context.uniform_4i(location, r, g, b, a),
3980            UnsignedInt(r) => gl_context.uniform_1ui(location, r),
3981            UnsignedIntVec2([r, g]) => gl_context.uniform_2ui(location, r, g),
3982            UnsignedIntVec3([r, g, b]) => gl_context.uniform_3ui(location, r, g, b),
3983            UnsignedIntVec4([r, g, b, a]) => gl_context.uniform_4ui(location, r, g, b, a),
3984            Matrix2 { transpose, matrix } => {
3985                gl_context.uniform_matrix_2fv(location, transpose, &matrix[..]);
3986            }
3987            Matrix3 { transpose, matrix } => {
3988                gl_context.uniform_matrix_3fv(location, transpose, &matrix[..]);
3989            }
3990            Matrix4 { transpose, matrix } => {
3991                gl_context.uniform_matrix_4fv(location, transpose, &matrix[..]);
3992            }
3993        }
3994    }
3995}
3996
3997#[repr(C)]
3998pub struct GlShader {
3999    pub program_id: GLuint,
4000    pub gl_context: GlContextPtr,
4001    /// AUDIT: guards against a double-drop deleting the same GL program twice
4002    /// (`drop_in_place` run twice on a byte-copied struct). Set `true` on
4003    /// construction; the first drop clears it so a second drop is a no-op.
4004    pub run_destructor: bool,
4005}
4006
4007impl ::core::fmt::Display for GlShader {
4008    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4009        write!(f, "GlShader {{ program_id: {} }}", self.program_id)
4010    }
4011}
4012
4013impl_traits_for_gl_object!(GlShader, program_id);
4014
4015impl Drop for GlShader {
4016    fn drop(&mut self) {
4017        // AUDIT: mirror `GlContextPtr::drop` — a C-ABI double-drop would call
4018        // `delete_program` on the same id twice. The first drop clears the flag.
4019        if !self.run_destructor {
4020            return;
4021        }
4022        self.run_destructor = false;
4023        self.gl_context.delete_program(self.program_id);
4024    }
4025}
4026
4027#[repr(C)]
4028#[derive(Clone)]
4029pub struct VertexShaderCompileError {
4030    pub error_id: i32,
4031    pub info_log: AzString,
4032}
4033
4034impl_traits_for_gl_object!(VertexShaderCompileError, error_id);
4035
4036impl ::core::fmt::Display for VertexShaderCompileError {
4037    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4038        write!(f, "E{}: {}", self.error_id, self.info_log)
4039    }
4040}
4041
4042#[repr(C)]
4043#[derive(Clone)]
4044pub struct FragmentShaderCompileError {
4045    pub error_id: i32,
4046    pub info_log: AzString,
4047}
4048
4049impl_traits_for_gl_object!(FragmentShaderCompileError, error_id);
4050
4051impl ::core::fmt::Display for FragmentShaderCompileError {
4052    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4053        write!(f, "E{}: {}", self.error_id, self.info_log)
4054    }
4055}
4056
4057#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
4058pub enum GlShaderCompileError {
4059    Vertex(VertexShaderCompileError),
4060    Fragment(FragmentShaderCompileError),
4061}
4062
4063impl ::core::fmt::Display for GlShaderCompileError {
4064    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4065        use self::GlShaderCompileError::{Fragment, Vertex};
4066        match self {
4067            Vertex(vert_err) => write!(f, "Failed to compile vertex shader: {vert_err}"),
4068            Fragment(frag_err) => write!(f, "Failed to compile fragment shader: {frag_err}"),
4069        }
4070    }
4071}
4072
4073impl ::core::fmt::Debug for GlShaderCompileError {
4074    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4075        write!(f, "{self}")
4076    }
4077}
4078
4079#[repr(C)]
4080#[derive(Clone)]
4081pub struct GlShaderLinkError {
4082    pub error_id: i32,
4083    pub info_log: AzString,
4084}
4085
4086impl_traits_for_gl_object!(GlShaderLinkError, error_id);
4087
4088impl ::core::fmt::Display for GlShaderLinkError {
4089    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4090        write!(f, "E{}: {}", self.error_id, self.info_log)
4091    }
4092}
4093
4094#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
4095pub enum GlShaderCreateError {
4096    Compile(GlShaderCompileError),
4097    Link(GlShaderLinkError),
4098    NoShaderCompiler,
4099}
4100
4101impl ::core::fmt::Display for GlShaderCreateError {
4102    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4103        use self::GlShaderCreateError::{Compile, Link, NoShaderCompiler};
4104        match self {
4105            Compile(compile_err) => write!(f, "Shader compile error: {compile_err}"),
4106            Link(link_err) => write!(f, "Shader linking error: {link_err}"),
4107            NoShaderCompiler => {
4108                write!(f, "OpenGL implementation doesn't include a shader compiler")
4109            }
4110        }
4111    }
4112}
4113
4114impl ::core::fmt::Debug for GlShaderCreateError {
4115    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
4116        write!(f, "{self}")
4117    }
4118}
4119
4120impl GlShader {
4121    /// Compiles and creates a new OpenGL shader, created from a vertex and a fragment shader
4122    /// string.
4123    ///
4124    /// If the shader fails to compile, the shader object gets automatically deleted, no cleanup
4125    /// necessary.
4126    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4127    /// # Errors
4128    ///
4129    /// Returns an error if the OpenGL implementation has no shader compiler, or if the vertex/fragment shader fails to compile or link.
4130    pub fn new(
4131        gl_context: &GlContextPtr,
4132        vertex_shader: &str,
4133        fragment_shader: &str,
4134    ) -> Result<Self, GlShaderCreateError> {
4135        // Check whether the OpenGL implementation supports a shader compiler...
4136        let mut shader_compiler_supported = [gl::FALSE as u8];
4137        gl_context.get_boolean_v(
4138            gl::SHADER_COMPILER,
4139            (&mut shader_compiler_supported[..]).into(),
4140        );
4141        if u32::from(shader_compiler_supported[0]) == gl::FALSE {
4142            // Implementation only supports binary shaders
4143            return Err(GlShaderCreateError::NoShaderCompiler);
4144        }
4145
4146        // Compile vertex shader
4147
4148        let vertex_shader_object = gl_context.create_shader(gl::VERTEX_SHADER);
4149        gl_context.shader_source(
4150            vertex_shader_object,
4151            vec![AzString::from(vertex_shader.to_string())].into(),
4152        );
4153        gl_context.compile_shader(vertex_shader_object);
4154
4155        if let Some(error_id) = get_gl_shader_error(gl_context, vertex_shader_object) {
4156            let info_log = gl_context.get_shader_info_log(vertex_shader_object);
4157            gl_context.delete_shader(vertex_shader_object);
4158            return Err(GlShaderCreateError::Compile(GlShaderCompileError::Vertex(
4159                VertexShaderCompileError { error_id, info_log },
4160            )));
4161        }
4162
4163        // Compile fragment shader
4164
4165        let fragment_shader_object = gl_context.create_shader(gl::FRAGMENT_SHADER);
4166        gl_context.shader_source(
4167            fragment_shader_object,
4168            vec![AzString::from(fragment_shader.to_string())].into(),
4169        );
4170        gl_context.compile_shader(fragment_shader_object);
4171
4172        if let Some(error_id) = get_gl_shader_error(gl_context, fragment_shader_object) {
4173            let info_log = gl_context.get_shader_info_log(fragment_shader_object);
4174            gl_context.delete_shader(vertex_shader_object);
4175            gl_context.delete_shader(fragment_shader_object);
4176            return Err(GlShaderCreateError::Compile(
4177                GlShaderCompileError::Fragment(FragmentShaderCompileError { error_id, info_log }),
4178            ));
4179        }
4180
4181        // Link program
4182
4183        let program_id = gl_context.create_program();
4184        gl_context.attach_shader(program_id, vertex_shader_object);
4185        gl_context.attach_shader(program_id, fragment_shader_object);
4186        gl_context.link_program(program_id);
4187
4188        if let Some(error_id) = get_gl_program_error(gl_context, program_id) {
4189            let info_log = gl_context.get_program_info_log(program_id);
4190            gl_context.delete_shader(vertex_shader_object);
4191            gl_context.delete_shader(fragment_shader_object);
4192            gl_context.delete_program(program_id);
4193            return Err(GlShaderCreateError::Link(GlShaderLinkError {
4194                error_id,
4195                info_log,
4196            }));
4197        }
4198
4199        gl_context.delete_shader(vertex_shader_object);
4200        gl_context.delete_shader(fragment_shader_object);
4201
4202        Ok(Self {
4203            program_id,
4204            gl_context: gl_context.clone(),
4205            run_destructor: true,
4206        })
4207    }
4208
4209    /// Draws vertex buffers, index buffers + uniforms to the texture
4210    ///
4211    /// # Panics
4212    ///
4213    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
4214    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4215    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
4216    pub fn draw(
4217        // shader to use for drawing
4218        shader_program_id: GLuint,
4219        // note: texture is &mut so the texture is reusable -
4220        texture: &mut Texture,
4221        // buffers + uniforms to draw
4222        buffers: &[(&VertexBuffer, &[Uniform])],
4223    ) {
4224        use alloc::collections::btree_map::BTreeMap;
4225
4226        const INDEX_TYPE: GLuint = gl::UNSIGNED_INT;
4227
4228        let texture_size = texture.size;
4229
4230        let gl_context = &texture.gl_context;
4231
4232        let saved = GlStateSave::save(gl_context);
4233
4234        // save draw()-specific state not covered by GlStateSave
4235        let mut current_blend_enabled = [0_u8];
4236        let mut current_primitive_restart_enabled = [0_u8];
4237        gl_context.get_boolean_v(gl::BLEND, (&mut current_blend_enabled[..]).into());
4238        gl_context.get_boolean_v(
4239            gl::PRIMITIVE_RESTART,
4240            (&mut current_primitive_restart_enabled[..]).into(),
4241        );
4242
4243        // 1. Create the framebuffer
4244        let framebuffers = gl_context.gen_framebuffers(1);
4245        let framebuffer_id = *framebuffers.get(0).unwrap();
4246        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
4247        // panic anywhere below (incl. `gen_renderbuffers().get(0).unwrap()`)
4248        // can't leak the FBO/RBO. Guard's Drop runs on unwind too.
4249        let mut fbo_rbo_guard = FboRboGuard {
4250            gl_context,
4251            framebuffer_id,
4252            renderbuffer_id: 0,
4253        };
4254        gl_context.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
4255
4256        let depthbuffers = gl_context.gen_renderbuffers(1);
4257        let depthbuffer_id = *depthbuffers.get(0).unwrap();
4258        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
4259
4260        gl_context.bind_texture(gl::TEXTURE_2D, texture.texture_id);
4261        gl_context.tex_image_2d(
4262            gl::TEXTURE_2D,
4263            0,
4264            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
4265            texture_size.width as i32,
4266            texture_size.height as i32,
4267            0,
4268            gl::RGBA, // gl::BGRA?
4269            gl::UNSIGNED_BYTE,
4270            None.into(),
4271        );
4272        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
4273        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
4274        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
4275        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
4276
4277        gl_context.bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
4278        gl_context.renderbuffer_storage(
4279            gl::RENDERBUFFER,
4280            gl::DEPTH_COMPONENT,
4281            texture_size.width as i32,
4282            texture_size.height as i32,
4283        );
4284        gl_context.framebuffer_renderbuffer(
4285            gl::FRAMEBUFFER,
4286            gl::DEPTH_ATTACHMENT,
4287            gl::RENDERBUFFER,
4288            depthbuffer_id,
4289        );
4290
4291        gl_context.framebuffer_texture_2d(
4292            gl::FRAMEBUFFER,
4293            gl::COLOR_ATTACHMENT0,
4294            gl::TEXTURE_2D,
4295            texture.texture_id,
4296            0,
4297        );
4298        gl_context.draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
4299
4300        #[cfg(feature = "std")]
4301        {
4302            let fb_check = gl_context.check_frame_buffer_status(gl::FRAMEBUFFER);
4303            match fb_check {
4304                gl::FRAMEBUFFER_COMPLETE => {}
4305                gl::FRAMEBUFFER_UNDEFINED => {
4306                    println!("GL_FRAMEBUFFER_UNDEFINED");
4307                }
4308                gl::FRAMEBUFFER_INCOMPLETE_ATTACHMENT => {
4309                    println!("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
4310                }
4311                gl::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT => {
4312                    println!("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
4313                }
4314                gl::FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER => {
4315                    println!("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
4316                }
4317                gl::FRAMEBUFFER_INCOMPLETE_READ_BUFFER => {
4318                    println!("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
4319                }
4320                gl::FRAMEBUFFER_UNSUPPORTED => {
4321                    println!("GL_FRAMEBUFFER_UNSUPPORTED");
4322                }
4323                gl::FRAMEBUFFER_INCOMPLETE_MULTISAMPLE => {
4324                    println!("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
4325                }
4326                gl::FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS => {
4327                    println!("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS");
4328                }
4329                o => {
4330                    println!("glFramebufferStatus returned unknown return code: {o}");
4331                }
4332            }
4333        }
4334
4335        gl_context.viewport(0, 0, texture_size.width as i32, texture_size.height as i32);
4336        gl_context.enable(gl::BLEND);
4337        // Use GL_PRIMITIVE_RESTART (OpenGL 3.1+) instead of
4338        // GL_PRIMITIVE_RESTART_FIXED_INDEX (4.3+) for macOS compatibility.
4339        gl_context.enable(gl::PRIMITIVE_RESTART);
4340        unsafe {
4341            let gl = gl_context.get();
4342            if !gl.glPrimitiveRestartIndex.is_null() {
4343                let func: extern "system" fn(u32) =
4344                    core::mem::transmute(gl.glPrimitiveRestartIndex);
4345                func(GL_RESTART_INDEX); // u32::MAX
4346            }
4347        }
4348        gl_context.disable(gl::MULTISAMPLE);
4349        gl_context.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); // TODO: enable / disable
4350        gl_context.use_program(shader_program_id);
4351
4352        // Avoid multiple calls to get_uniform_location by caching the uniform locations
4353        let mut uniform_locations: BTreeMap<AzString, i32> = BTreeMap::new();
4354        let mut max_uniform_len = 0;
4355        for (_, uniforms) in buffers {
4356            for uniform in *uniforms {
4357                if !uniform_locations.contains_key(&uniform.uniform_name) {
4358                    uniform_locations.insert(
4359                        uniform.uniform_name.clone(),
4360                        gl_context
4361                            .get_uniform_location(shader_program_id, uniform.uniform_name.as_str()),
4362                    );
4363                }
4364            }
4365            max_uniform_len = max_uniform_len.max(uniforms.len());
4366        }
4367        let mut current_uniforms = vec![None; max_uniform_len];
4368
4369        // Since the description of the vertex buffers is always the same,
4370        // only the first layer needs to bind its VAO
4371
4372        // Draw the actual layers
4373        for (vertex_index_buffer, uniforms) in buffers {
4374            gl_context.bind_vertex_array(vertex_index_buffer.vao.vao_id);
4375            gl_context.bind_buffer(gl::ARRAY_BUFFER, vertex_index_buffer.vertex_buffer_id);
4376            gl_context.bind_buffer(
4377                gl::ELEMENT_ARRAY_BUFFER,
4378                vertex_index_buffer.index_buffer_id,
4379            );
4380
4381            // Only set the uniform if the value has changed
4382            for (uniform_index, uniform) in uniforms.iter().enumerate() {
4383                if current_uniforms[uniform_index] != Some(uniform.uniform_type) {
4384                    let uniform_location = uniform_locations[&uniform.uniform_name];
4385                    uniform.uniform_type.set(gl_context.get(), uniform_location);
4386                    current_uniforms[uniform_index] = Some(uniform.uniform_type);
4387                }
4388            }
4389
4390            gl_context.draw_elements(
4391                vertex_index_buffer.index_buffer_format.get_gl_id(),
4392                vertex_index_buffer.index_buffer_len as i32,
4393                INDEX_TYPE,
4394                0,
4395            );
4396        }
4397
4398        // Reset draw()-specific state
4399        if u32::from(current_blend_enabled[0]) == gl::FALSE {
4400            gl_context.disable(gl::BLEND);
4401        }
4402        if u32::from(current_primitive_restart_enabled[0]) == gl::FALSE {
4403            gl_context.disable(gl::PRIMITIVE_RESTART);
4404        }
4405
4406        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
4407        // also runs on an unwinding panic) — reclaim them explicitly here so
4408        // the deletion order matches the original (delete before texture
4409        // metadata writes / after state restore).
4410        // Reset common GL state
4411        saved.restore(gl_context);
4412        drop(fbo_rbo_guard);
4413
4414        texture.format = RawImageFormat::RGBA8;
4415        texture.flags = TextureFlags {
4416            is_opaque: false,
4417            is_video_texture: false,
4418        };
4419    }
4420}
4421
4422#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4423fn get_gl_shader_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
4424    let mut err = [0];
4425    context.get_shader_iv(shader_object, gl::COMPILE_STATUS, (&mut err[..]).into());
4426    let err_code = err[0];
4427    if err_code == gl::TRUE as i32 {
4428        None
4429    } else {
4430        Some(err_code)
4431    }
4432}
4433
4434#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
4435fn get_gl_program_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
4436    let mut err = [0];
4437    context.get_program_iv(shader_object, gl::LINK_STATUS, (&mut err[..]).into());
4438    let err_code = err[0];
4439    if err_code == gl::TRUE as i32 {
4440        None
4441    } else {
4442        Some(err_code)
4443    }
4444}
4445
4446#[cfg(test)]
4447#[path = "gl_test.rs"]
4448mod gl_test;