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)]
5
use alloc::{
6
    boxed::Box,
7
    rc::Rc,
8
    string::{String, ToString},
9
    vec::Vec,
10
};
11
use core::{
12
    ffi, fmt,
13
    hash::{Hash, Hasher},
14
    mem::ManuallyDrop,
15
    sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
16
};
17

            
18
use azul_css::{
19
    props::{
20
        basic::{ColorF, ColorU},
21
        style::StyleTransformVec,
22
    },
23
    AzString, OptionI32, OptionU32, OptionUsize, StringVec, U8Vec,
24
};
25
pub use gl_context_loader::{
26
    ctypes::*, gl, GLeglImageOES, GLsync, GLvoid, GenericGlContext, GlType as GlContextGlType,
27
};
28

            
29
pub use crate::glconst::*;
30
use 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

            
42
pub type GLuint = u32;
43
pub type GLint = i32;
44
pub type GLint64 = i64;
45
pub type GLuint64 = u64;
46
pub type GLenum = u32;
47
pub type GLintptr = isize;
48
pub type GLboolean = u8;
49
pub type GLsizeiptr = isize;
50
pub type GLbitfield = u32;
51
pub type GLsizei = i32;
52
pub type GLclampf = f32;
53
pub type GLfloat = f32;
54

            
55
pub 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)]
61
pub struct GlVoidPtrConst {
62
    pub ptr: *const GLvoid,
63
    pub run_destructor: bool,
64
}
65

            
66
impl Clone for GlVoidPtrConst {
67
    fn clone(&self) -> Self {
68
        Self {
69
            ptr: self.ptr,
70
            run_destructor: true,
71
        }
72
    }
73
}
74

            
75
impl Drop for GlVoidPtrConst {
76
17
    fn drop(&mut self) {
77
17
        self.run_destructor = false;
78
17
    }
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)]
87
pub struct GlVoidPtrMut {
88
    pub ptr: *mut GLvoid,
89
}
90

            
91
impl 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)]
99
pub struct Refstr {
100
    pub ptr: *const u8,
101
    pub len: usize,
102
}
103

            
104
impl Clone for Refstr {
105
1
    fn clone(&self) -> Self {
106
1
        Self {
107
1
            ptr: self.ptr,
108
1
            len: self.len,
109
1
        }
110
1
    }
111
}
112

            
113
impl fmt::Debug for Refstr {
114
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115
1
        self.as_str().fmt(f)
116
1
    }
117
}
118

            
119
impl Refstr {
120
21
    #[must_use] pub const fn as_str(&self) -> &str {
121
        // AUDIT: `from_raw_parts`/`from_utf8_unchecked` are UB on a null ptr
122
        // (even with len==0). FFI callers can hand us a null/empty Refstr, so
123
        // return an empty `&str` instead of forming a slice over null.
124
21
        if self.ptr.is_null() || self.len == 0 {
125
5
            return "";
126
16
        }
127
16
        unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.ptr, self.len)) }
128
21
    }
129
}
130

            
131
impl From<&str> for Refstr {
132
11
    fn from(s: &str) -> Self {
133
11
        Self {
134
11
            ptr: s.as_ptr(),
135
11
            len: s.len(),
136
11
        }
137
11
    }
138
}
139

            
140
/// FFI-safe wrapper for `&[&str]`.
141
#[repr(C)]
142
pub struct RefstrVecRef {
143
    pub ptr: *const Refstr,
144
    pub len: usize,
145
}
146

            
147
impl Clone for RefstrVecRef {
148
    fn clone(&self) -> Self {
149
        Self {
150
            ptr: self.ptr,
151
            len: self.len,
152
        }
153
    }
154
}
155

            
156
impl fmt::Debug for RefstrVecRef {
157
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158
        self.as_slice().fmt(f)
159
    }
160
}
161

            
162
impl RefstrVecRef {
163
5
    #[must_use] pub const fn as_slice(&self) -> &[Refstr] {
164
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
165
5
        if self.ptr.is_null() || self.len == 0 {
166
4
            return &[];
167
1
        }
168
1
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
169
5
    }
170
}
171

            
172
impl From<&[Refstr]> for RefstrVecRef {
173
2
    fn from(s: &[Refstr]) -> Self {
174
2
        Self {
175
2
            ptr: s.as_ptr(),
176
2
            len: s.len(),
177
2
        }
178
2
    }
179
}
180

            
181
/// FFI-safe wrapper for `&mut [GLint64]`.
182
#[repr(C)]
183
pub struct GLint64VecRefMut {
184
    pub ptr: *mut i64,
185
    pub len: usize,
186
}
187

            
188
impl Clone for GLint64VecRefMut {
189
    fn clone(&self) -> Self {
190
        Self {
191
            ptr: self.ptr,
192
            len: self.len,
193
        }
194
    }
195
}
196

            
197
impl fmt::Debug for GLint64VecRefMut {
198
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199
        self.as_slice().fmt(f)
200
    }
201
}
202

            
203
impl From<&mut [GLint64]> for GLint64VecRefMut {
204
1
    fn from(s: &mut [GLint64]) -> Self {
205
1
        Self {
206
1
            ptr: s.as_mut_ptr(),
207
1
            len: s.len(),
208
1
        }
209
1
    }
210
}
211

            
212
impl GLint64VecRefMut {
213
2
    #[must_use] pub const fn as_slice(&self) -> &[GLint64] {
214
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
215
2
        if self.ptr.is_null() || self.len == 0 {
216
1
            return &[];
217
1
        }
218
1
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
219
2
    }
220
3
    const fn as_mut_slice(&mut self) -> &mut [GLint64] {
221
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
222
3
        if self.ptr.is_null() || self.len == 0 {
223
1
            return &mut [];
224
2
        }
225
2
        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
226
3
    }
227
}
228

            
229
/// FFI-safe wrapper for `&mut [GLfloat]`.
230
#[repr(C)]
231
pub struct GLfloatVecRefMut {
232
    pub ptr: *mut f32,
233
    pub len: usize,
234
}
235

            
236
impl Clone for GLfloatVecRefMut {
237
    fn clone(&self) -> Self {
238
        Self {
239
            ptr: self.ptr,
240
            len: self.len,
241
        }
242
    }
243
}
244

            
245
impl fmt::Debug for GLfloatVecRefMut {
246
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247
        self.as_slice().fmt(f)
248
    }
249
}
250

            
251
impl From<&mut [GLfloat]> for GLfloatVecRefMut {
252
2
    fn from(s: &mut [GLfloat]) -> Self {
253
2
        Self {
254
2
            ptr: s.as_mut_ptr(),
255
2
            len: s.len(),
256
2
        }
257
2
    }
258
}
259

            
260
impl GLfloatVecRefMut {
261
1
    #[must_use] pub const fn as_slice(&self) -> &[GLfloat] {
262
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
263
1
        if self.ptr.is_null() || self.len == 0 {
264
1
            return &[];
265
        }
266
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
267
1
    }
268
4
    const fn as_mut_slice(&mut self) -> &mut [GLfloat] {
269
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
270
4
        if self.ptr.is_null() || self.len == 0 {
271
2
            return &mut [];
272
2
        }
273
2
        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
274
4
    }
275
}
276

            
277
/// FFI-safe wrapper for `&mut [GLint]`.
278
#[repr(C)]
279
pub struct GLintVecRefMut {
280
    pub ptr: *mut i32,
281
    pub len: usize,
282
}
283

            
284
impl Clone for GLintVecRefMut {
285
    fn clone(&self) -> Self {
286
        Self {
287
            ptr: self.ptr,
288
            len: self.len,
289
        }
290
    }
291
}
292

            
293
impl fmt::Debug for GLintVecRefMut {
294
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295
        self.as_slice().fmt(f)
296
    }
297
}
298

            
299
impl From<&mut [GLint]> for GLintVecRefMut {
300
10
    fn from(s: &mut [GLint]) -> Self {
301
10
        Self {
302
10
            ptr: s.as_mut_ptr(),
303
10
            len: s.len(),
304
10
        }
305
10
    }
306
}
307

            
308
impl GLintVecRefMut {
309
1
    #[must_use] pub const fn as_slice(&self) -> &[GLint] {
310
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
311
1
        if self.ptr.is_null() || self.len == 0 {
312
1
            return &[];
313
        }
314
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
315
1
    }
316
12
    const fn as_mut_slice(&mut self) -> &mut [GLint] {
317
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
318
12
        if self.ptr.is_null() || self.len == 0 {
319
2
            return &mut [];
320
10
        }
321
10
        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
322
12
    }
323
}
324

            
325
/// FFI-safe wrapper for `&[GLuint]`.
326
#[repr(C)]
327
pub struct GLuintVecRef {
328
    pub ptr: *const u32,
329
    pub len: usize,
330
}
331

            
332
impl Clone for GLuintVecRef {
333
    fn clone(&self) -> Self {
334
        Self {
335
            ptr: self.ptr,
336
            len: self.len,
337
        }
338
    }
339
}
340

            
341
impl fmt::Debug for GLuintVecRef {
342
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343
        self.as_slice().fmt(f)
344
    }
345
}
346

            
347
impl From<&[GLuint]> for GLuintVecRef {
348
36
    fn from(s: &[GLuint]) -> Self {
349
36
        Self {
350
36
            ptr: s.as_ptr(),
351
36
            len: s.len(),
352
36
        }
353
36
    }
354
}
355

            
356
impl GLuintVecRef {
357
43
    #[must_use] pub const fn as_slice(&self) -> &[GLuint] {
358
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
359
43
        if self.ptr.is_null() || self.len == 0 {
360
13
            return &[];
361
30
        }
362
30
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
363
43
    }
364
}
365

            
366
/// FFI-safe wrapper for `&[GLenum]`.
367
#[repr(C)]
368
pub struct GLenumVecRef {
369
    pub ptr: *const u32,
370
    pub len: usize,
371
}
372

            
373
impl Clone for GLenumVecRef {
374
    fn clone(&self) -> Self {
375
        Self {
376
            ptr: self.ptr,
377
            len: self.len,
378
        }
379
    }
380
}
381

            
382
impl fmt::Debug for GLenumVecRef {
383
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384
        self.as_slice().fmt(f)
385
    }
386
}
387

            
388
impl From<&[GLenum]> for GLenumVecRef {
389
2
    fn from(s: &[GLenum]) -> Self {
390
2
        Self {
391
2
            ptr: s.as_ptr(),
392
2
            len: s.len(),
393
2
        }
394
2
    }
395
}
396

            
397
impl GLenumVecRef {
398
4
    #[must_use] pub const fn as_slice(&self) -> &[GLenum] {
399
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
400
4
        if self.ptr.is_null() || self.len == 0 {
401
3
            return &[];
402
1
        }
403
1
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
404
4
    }
405
}
406

            
407
/// FFI-safe wrapper for `&[u8]`.
408
#[repr(C)]
409
pub struct U8VecRef {
410
    pub ptr: *const u8,
411
    pub len: usize,
412
}
413

            
414
impl Clone for U8VecRef {
415
1
    fn clone(&self) -> Self {
416
1
        Self {
417
1
            ptr: self.ptr,
418
1
            len: self.len,
419
1
        }
420
1
    }
421
}
422

            
423
impl From<&[u8]> for U8VecRef {
424
130
    fn from(s: &[u8]) -> Self {
425
130
        Self {
426
130
            ptr: s.as_ptr(),
427
130
            len: s.len(),
428
130
        }
429
130
    }
430
}
431

            
432
impl U8VecRef {
433
153
    #[must_use] pub const fn as_slice(&self) -> &[u8] {
434
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
435
153
        if self.ptr.is_null() || self.len == 0 {
436
84
            return &[];
437
69
        }
438
69
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
439
153
    }
440
}
441

            
442
impl fmt::Debug for U8VecRef {
443
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444
        self.as_slice().fmt(f)
445
    }
446
}
447

            
448
impl PartialOrd for U8VecRef {
449
1
    fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
450
1
        self.as_slice().partial_cmp(rhs.as_slice())
451
1
    }
452
}
453

            
454
impl Ord for U8VecRef {
455
3
    fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
456
3
        self.as_slice().cmp(rhs.as_slice())
457
3
    }
458
}
459

            
460
impl PartialEq for U8VecRef {
461
2
    fn eq(&self, rhs: &Self) -> bool {
462
2
        self.as_slice().eq(rhs.as_slice())
463
2
    }
464
}
465

            
466
impl Eq for U8VecRef {}
467

            
468
impl Hash for U8VecRef {
469
4
    fn hash<H>(&self, state: &mut H)
470
4
    where
471
4
        H: Hasher,
472
    {
473
4
        self.as_slice().hash(state);
474
4
    }
475
}
476

            
477
/// FFI-safe wrapper for `&[f32]`.
478
#[repr(C)]
479
pub struct F32VecRef {
480
    pub ptr: *const f32,
481
    pub len: usize,
482
}
483

            
484
impl Clone for F32VecRef {
485
    fn clone(&self) -> Self {
486
        Self {
487
            ptr: self.ptr,
488
            len: self.len,
489
        }
490
    }
491
}
492

            
493
impl fmt::Debug for F32VecRef {
494
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495
        self.as_slice().fmt(f)
496
    }
497
}
498

            
499
impl From<&[f32]> for F32VecRef {
500
1
    fn from(s: &[f32]) -> Self {
501
1
        Self {
502
1
            ptr: s.as_ptr(),
503
1
            len: s.len(),
504
1
        }
505
1
    }
506
}
507

            
508
impl F32VecRef {
509
2
    #[must_use] pub const fn as_slice(&self) -> &[f32] {
510
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
511
2
        if self.ptr.is_null() || self.len == 0 {
512
1
            return &[];
513
1
        }
514
1
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
515
2
    }
516
}
517

            
518
/// FFI-safe wrapper for `&[i32]`.
519
#[repr(C)]
520
pub struct I32VecRef {
521
    pub ptr: *const i32,
522
    pub len: usize,
523
}
524

            
525
impl Clone for I32VecRef {
526
    fn clone(&self) -> Self {
527
        Self {
528
            ptr: self.ptr,
529
            len: self.len,
530
        }
531
    }
532
}
533

            
534
impl fmt::Debug for I32VecRef {
535
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536
        self.as_slice().fmt(f)
537
    }
538
}
539

            
540
impl From<&[i32]> for I32VecRef {
541
1
    fn from(s: &[i32]) -> Self {
542
1
        Self {
543
1
            ptr: s.as_ptr(),
544
1
            len: s.len(),
545
1
        }
546
1
    }
547
}
548

            
549
impl I32VecRef {
550
2
    #[must_use] pub const fn as_slice(&self) -> &[i32] {
551
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
552
2
        if self.ptr.is_null() || self.len == 0 {
553
1
            return &[];
554
1
        }
555
1
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
556
2
    }
557
}
558

            
559
/// FFI-safe wrapper for `&mut [GLboolean]` (i.e. `&mut [u8]`).
560
#[repr(C)]
561
pub struct GLbooleanVecRefMut {
562
    pub ptr: *mut u8,
563
    pub len: usize,
564
}
565

            
566
impl Clone for GLbooleanVecRefMut {
567
    fn clone(&self) -> Self {
568
        Self {
569
            ptr: self.ptr,
570
            len: self.len,
571
        }
572
    }
573
}
574

            
575
impl fmt::Debug for GLbooleanVecRefMut {
576
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577
        self.as_slice().fmt(f)
578
    }
579
}
580

            
581
impl From<&mut [GLboolean]> for GLbooleanVecRefMut {
582
7
    fn from(s: &mut [GLboolean]) -> Self {
583
7
        Self {
584
7
            ptr: s.as_mut_ptr(),
585
7
            len: s.len(),
586
7
        }
587
7
    }
588
}
589

            
590
impl GLbooleanVecRefMut {
591
1
    #[must_use] pub const fn as_slice(&self) -> &[GLboolean] {
592
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
593
1
        if self.ptr.is_null() || self.len == 0 {
594
1
            return &[];
595
        }
596
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
597
1
    }
598
8
    const fn as_mut_slice(&mut self) -> &mut [GLboolean] {
599
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
600
8
        if self.ptr.is_null() || self.len == 0 {
601
1
            return &mut [];
602
7
        }
603
7
        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
604
8
    }
605
}
606

            
607
/// FFI-safe wrapper for `&mut [u8]`.
608
#[repr(C)]
609
pub struct U8VecRefMut {
610
    pub ptr: *mut u8,
611
    pub len: usize,
612
}
613

            
614
impl Clone for U8VecRefMut {
615
    fn clone(&self) -> Self {
616
        Self {
617
            ptr: self.ptr,
618
            len: self.len,
619
        }
620
    }
621
}
622

            
623
impl fmt::Debug for U8VecRefMut {
624
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625
        self.as_slice().fmt(f)
626
    }
627
}
628

            
629
impl From<&mut [u8]> for U8VecRefMut {
630
2
    fn from(s: &mut [u8]) -> Self {
631
2
        Self {
632
2
            ptr: s.as_mut_ptr(),
633
2
            len: s.len(),
634
2
        }
635
2
    }
636
}
637

            
638
impl U8VecRefMut {
639
1
    #[must_use] pub const fn as_slice(&self) -> &[u8] {
640
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
641
1
        if self.ptr.is_null() || self.len == 0 {
642
1
            return &[];
643
        }
644
        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
645
1
    }
646
4
    const fn as_mut_slice(&mut self) -> &mut [u8] {
647
        // AUDIT: `from_raw_parts` is UB on a null ptr; guard FFI null/empty.
648
4
        if self.ptr.is_null() || self.len == 0 {
649
2
            return &mut [];
650
2
        }
651
2
        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
652
4
    }
653
}
654

            
655
impl_option!(
656
    U8VecRef,
657
    OptionU8VecRef,
658
    copy = false,
659
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
660
);
661

            
662
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
663
#[repr(C)]
664
pub struct DebugMessage {
665
    pub message: AzString,
666
    pub source: GLenum,
667
    pub ty: GLenum,
668
    pub id: GLenum,
669
    pub severity: GLenum,
670
}
671

            
672
impl_option!(
673
    DebugMessage,
674
    OptionDebugMessage,
675
    copy = false,
676
    [Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash]
677
);
678

            
679
impl_vec!(DebugMessage, DebugMessageVec, DebugMessageVecDestructor, DebugMessageVecDestructorType, DebugMessageVecSlice, OptionDebugMessage);
680
impl_vec_debug!(DebugMessage, DebugMessageVec);
681
impl_vec_partialord!(DebugMessage, DebugMessageVec);
682
impl_vec_ord!(DebugMessage, DebugMessageVec);
683
impl_vec_clone!(DebugMessage, DebugMessageVec, DebugMessageVecDestructor);
684
impl_vec_partialeq!(DebugMessage, DebugMessageVec);
685
impl_vec_eq!(DebugMessage, DebugMessageVec);
686
impl_vec_hash!(DebugMessage, DebugMessageVec);
687

            
688
impl_vec!(GLint, GLintVec, GLintVecDestructor, GLintVecDestructorType, GLintVecSlice, OptionI32);
689
impl_vec_debug!(GLint, GLintVec);
690
impl_vec_partialord!(GLint, GLintVec);
691
impl_vec_ord!(GLint, GLintVec);
692
impl_vec_clone!(GLint, GLintVec, GLintVecDestructor);
693
impl_vec_partialeq!(GLint, GLintVec);
694
impl_vec_eq!(GLint, GLintVec);
695
impl_vec_hash!(GLint, GLintVec);
696

            
697
impl_vec!(GLuint, GLuintVec, GLuintVecDestructor, GLuintVecDestructorType, GLuintVecSlice, OptionU32);
698
impl_vec_debug!(GLuint, GLuintVec);
699
impl_vec_partialord!(GLuint, GLuintVec);
700
impl_vec_ord!(GLuint, GLuintVec);
701
impl_vec_clone!(GLuint, GLuintVec, GLuintVecDestructor);
702
impl_vec_partialeq!(GLuint, GLuintVec);
703
impl_vec_eq!(GLuint, GLuintVec);
704
impl_vec_hash!(GLuint, GLuintVec);
705

            
706
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
707
#[repr(C)]
708
pub enum GlType {
709
    Gl,
710
    Gles,
711
}
712

            
713
impl From<GlContextGlType> for GlType {
714
4
    fn from(a: GlContextGlType) -> Self {
715
4
        match a {
716
3
            GlContextGlType::Gl => Self::Gl,
717
1
            GlContextGlType::GlEs => Self::Gles,
718
        }
719
4
    }
720
}
721

            
722
// (U8Vec, u32)
723
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
724
#[repr(C)]
725
// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
726
#[allow(clippy::pub_underscore_fields)]
727
pub struct GetProgramBinaryReturn {
728
    pub _0: U8Vec,
729
    pub _1: u32,
730
}
731

            
732
// (i32, u32, AzString)
733
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
734
#[repr(C)]
735
// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
736
#[allow(clippy::pub_underscore_fields)]
737
pub struct GetActiveAttribReturn {
738
    pub _0: i32,
739
    pub _1: u32,
740
    pub _2: AzString,
741
}
742

            
743
// (i32, u32, AzString)
744
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
745
#[repr(C)]
746
// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
747
#[allow(clippy::pub_underscore_fields)]
748
pub struct GetActiveUniformReturn {
749
    pub _0: i32,
750
    pub _1: u32,
751
    pub _2: AzString,
752
}
753

            
754
#[repr(C)]
755
pub struct GLsyncPtr {
756
    pub ptr: *const c_void, /* *const __GLsync */
757
    pub run_destructor: bool,
758
}
759

            
760
impl Clone for GLsyncPtr {
761
2
    fn clone(&self) -> Self {
762
2
        Self {
763
2
            ptr: self.ptr,
764
2
            run_destructor: true,
765
2
        }
766
2
    }
767
}
768

            
769
impl fmt::Debug for GLsyncPtr {
770
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771
1
        write!(f, "0x{:0x}", self.ptr as usize)
772
1
    }
773
}
774

            
775
impl GLsyncPtr {
776
2
    #[must_use] pub const fn new(p: GLsync) -> Self {
777
2
        Self {
778
2
            ptr: p,
779
2
            run_destructor: true,
780
2
        }
781
2
    }
782
2
    #[must_use] pub fn get(self) -> GLsync {
783
2
        self.ptr as GLsync
784
2
    }
785
}
786

            
787
impl Drop for GLsyncPtr {
788
4
    fn drop(&mut self) {
789
4
        self.run_destructor = false;
790
4
    }
791
}
792

            
793
/// Each pipeline (window) has its own OpenGL textures. GL Textures can technically
794
/// be shared across pipelines, however this turns out to be very difficult in practice.
795
pub type GlTextureStorage = OrderedMap<Epoch, OrderedMap<ExternalImageId, Texture>>;
796

            
797
/// Non-cleaned up textures. When a `GlTexture` is registered, it has to stay active as long
798
/// as `WebRender` needs it for drawing. To transparently do this, we store the epoch that the
799
/// texture was originally created with, and check, **after we have drawn the frame**,
800
/// if there are any textures that need cleanup.
801
///
802
/// Because the Texture2d is wrapped in an Rc, the destructor (which cleans up the OpenGL
803
/// texture) does not run until we remove the textures
804
///
805
/// Note: Because textures could be used after the current draw call (ex. for scrolling),
806
/// the `ACTIVE_GL_TEXTURES` are indexed by their epoch. Use `renderer.flush_pipeline_info()`
807
/// to see which textures are still active and which ones can be safely removed.
808
///
809
/// See: <https://github.com/servo/webrender/issues/2940>
810
///
811
/// WARNING: Not thread-safe (however, the Texture itself is thread-unsafe, so it's unlikely to ever
812
/// be misused)
813
static mut ACTIVE_GL_TEXTURES: Option<OrderedMap<DocumentId, GlTextureStorage>> = None;
814

            
815
/// Sound accessor for the process-global GL texture table.
816
///
817
/// AUDIT: GL access is single-threaded by design (see the WARNING above — the
818
/// `Texture` itself is thread-unsafe), so no lock is used. The soundness fix
819
/// here is to never form an *implicit* reference to the `static mut` (the
820
/// edition-2024 `static_mut_refs` hard error + `&mut`-aliasing UB that
821
/// `ACTIVE_GL_TEXTURES.as_mut()` / `.as_ref()` triggered). Deriving the
822
/// reference from `&raw mut` gives it correct provenance without ever naming
823
/// the static as an auto-ref place. Callers must not hold two of these at once
824
/// (they don't — every use is a single non-reentrant scope).
825
#[inline]
826
#[allow(clippy::deref_addrof)] // the `&raw mut` deref is deliberate: it avoids naming the static as an auto-ref place (edition-2024 `static_mut_refs`)
827
33
fn active_gl_textures() -> &'static mut Option<OrderedMap<DocumentId, GlTextureStorage>> {
828
    // SAFETY: `&raw mut` avoids an intermediate `&mut ACTIVE_GL_TEXTURES`; the
829
    // static is valid for the whole program. Single-threaded access (GL thread).
830
33
    unsafe { &mut *(&raw mut ACTIVE_GL_TEXTURES) }
831
33
}
832

            
833
/// Inserts a new texture into the OpenGL texture cache, returns a new image ID
834
/// for the inserted texture
835
///
836
/// This function exists so azul doesn't have to use `lazy_static` as a dependency
837
///
838
/// # Panics
839
///
840
/// Panics if the global active-GL-texture table has not been initialized.
841
#[must_use]
842
5
pub fn insert_into_active_gl_textures(
843
5
    document_id: DocumentId,
844
5
    epoch: Epoch,
845
5
    texture: Texture,
846
5
) -> ExternalImageId {
847
5
    let external_image_id = ExternalImageId::new();
848

            
849
5
    let active = active_gl_textures();
850
5
    if active.is_none() {
851
1
        *active = Some(OrderedMap::new());
852
4
    }
853
5
    let active_textures = active.as_mut().unwrap();
854
5
    let active_epochs = active_textures.entry(document_id).or_default();
855
5
    let active_textures_for_epoch = active_epochs.entry(epoch).or_default();
856
5
    active_textures_for_epoch.insert(external_image_id, texture);
857

            
858
5
    external_image_id
859
5
}
860

            
861
/// Destroys all textures from the given `document_id`
862
/// where the texture is **older** than the given `epoch`.
863
3
pub fn gl_textures_remove_epochs_from_pipeline(document_id: &DocumentId, epoch: Epoch) {
864
    // TODO: Handle overflow of Epochs correctly (low priority)
865
3
    let Some(active_textures) = active_gl_textures().as_mut() else {
866
1
        return;
867
    };
868

            
869
2
    let Some(active_epochs) = active_textures.get_mut(document_id) else {
870
1
        return;
871
    };
872

            
873
    // NOTE: original code used retain() but that
874
    // doesn't work on no_std
875
1
    let mut epochs_to_remove = Vec::new();
876

            
877
2
    for (gl_texture_epoch, _) in active_epochs.iter() {
878
2
        if *gl_texture_epoch < epoch {
879
1
            epochs_to_remove.push(*gl_texture_epoch);
880
1
        }
881
    }
882

            
883
2
    for epoch in epochs_to_remove {
884
1
        active_epochs.remove(&epoch);
885
1
    }
886
3
}
887

            
888
// document_id, epoch, external_image_id
889
5
#[must_use] pub fn remove_single_texture_from_active_gl_textures(
890
5
    document_id: &DocumentId,
891
5
    epoch: &Epoch,
892
5
    external_image_id: &ExternalImageId,
893
5
) -> Option<()> {
894
5
    let active_textures = active_gl_textures().as_mut()?;
895
4
    let epochs = active_textures.get_mut(document_id)?;
896
3
    let images_in_epoch = epochs.get_mut(epoch)?;
897
2
    images_in_epoch.remove(external_image_id);
898
2
    Some(())
899
5
}
900

            
901
/// Removes a `DocumentId` from the active epochs
902
2
pub fn gl_textures_remove_active_pipeline(document_id: &DocumentId) {
903
2
    let Some(active_textures) = active_gl_textures().as_mut() else {
904
1
        return;
905
    };
906
1
    active_textures.remove(document_id);
907
2
}
908

            
909
/// Destroys all textures, usually done before destroying the OpenGL context
910
#[allow(clippy::cast_precision_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
911
4
pub fn gl_textures_clear_opengl_cache() {
912
4
    *active_gl_textures() = None;
913
4
}
914

            
915
// Search all epoch hash maps for the given key
916
// There does not seem to be a way to get the epoch for the key,
917
// so we simply have to search all active epochs
918
//
919
// NOTE: Invalid textures can be generated on minimize / maximize
920
// Luckily, webrender simply ignores an invalid texture, so we don't
921
// need to check whether a window is maximized or minimized - if
922
// we encounter an invalid ID, webrender simply won't draw anything,
923
// but at least it won't crash. Usually invalid textures are also 0x0
924
// pixels large - so it's not like we had anything to draw anyway.
925
#[allow(clippy::cast_precision_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
926
14
#[must_use] pub fn get_opengl_texture(image_key: &ExternalImageId) -> Option<(GLuint, (f32, f32))> {
927
14
    let active_textures = active_gl_textures().as_ref()?;
928
12
    active_textures
929
12
        .values()
930
12
        .flat_map(|active_document| active_document.values())
931
14
        .find_map(|active_epoch| active_epoch.get(image_key))
932
12
        .map(|tex| {
933
7
            (
934
7
                tex.texture_id,
935
7
                (tex.size.width as f32, tex.size.height as f32),
936
7
            )
937
7
        })
938
14
}
939

            
940
/// For .`get_gl_precision_format()`, but ABI-safe - returning an array or a tuple is not ABI-safe
941
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
942
#[repr(C)]
943
// `_0`/`_1`… are C-ABI tuple-payload field names exposed in api.json; cannot rename.
944
#[allow(clippy::pub_underscore_fields)]
945
pub struct GlShaderPrecisionFormatReturn {
946
    pub _0: GLint,
947
    pub _1: GLint,
948
    pub _2: GLint,
949
}
950

            
951
#[repr(C)]
952
pub struct GlContextPtr {
953
    /// `ManuallyDrop` so the owned `Box` is freed ONLY when `run_destructor` is
954
    /// still set (see `Drop`). The codegen FFI wrappers (`AzTexture` etc.) embed
955
    /// this by value AND have their own `Drop` that `drop_in_place`s the real
956
    /// type first; Rust's drop glue would then drop this field a SECOND time on
957
    /// the same bytes. Gating the `Box` free on `run_destructor` (which the first
958
    /// drop clears in the shared memory) makes that second drop a safe no-op.
959
    /// Layout is unchanged: `ManuallyDrop<Box<T>>` is a single pointer, identical
960
    /// to the old `Box<T>` and to the FFI `*mut c_void`.
961
    pub ptr: ManuallyDrop<Box<Rc<GlContextPtrInner>>>,
962
    /// Whether to force a hardware or software renderer
963
    pub renderer_type: RendererType,
964
    pub run_destructor: bool,
965
}
966

            
967
impl Clone for GlContextPtr {
968
53
    fn clone(&self) -> Self {
969
53
        Self {
970
53
            ptr: ManuallyDrop::new((*self.ptr).clone()),
971
53
            renderer_type: self.renderer_type,
972
53
            run_destructor: true,
973
53
        }
974
53
    }
975
}
976

            
977
impl Drop for GlContextPtr {
978
260
    fn drop(&mut self) {
979
        // Only free the owned Box if this instance still owns it. The FFI wrapper
980
        // double-drop (see the struct doc) hits these same bytes a second time
981
        // with `run_destructor` already cleared by the first drop -> no-op, no
982
        // double-free.
983
260
        if self.run_destructor {
984
260
            self.run_destructor = false;
985
260
            unsafe { ManuallyDrop::drop(&mut self.ptr); }
986
        }
987
260
    }
988
}
989

            
990
impl GlContextPtr {
991
2
    #[must_use] pub fn get_svg_shader(&self) -> GLuint {
992
2
        self.ptr.svg_shader
993
2
    }
994
    /// Whether this hardware GL context proved usable at construction (the SVG
995
    /// shaders compiled+linked at some GLSL version). `false` means context
996
    /// creation succeeded but the driver can't run our shaders -- the caller
997
    /// should fall back to CPU rendering. Always `false` for a Software context
998
    /// (which never compiles these shaders); only meaningful on the GPU path.
999
2
    #[must_use] pub fn is_gl_usable(&self) -> bool {
2
        self.ptr.svg_shader != 0
2
    }
    /// The GLSL `#version` the driver accepted at construction (e.g. "150" or
    /// "300 es"), discovered by the probe. Empty string if the context is
    /// unusable / software. Exposed in the API so apps can report/branch on it.
2
    #[must_use] pub fn get_usable_glsl_version(&self) -> AzString {
2
        self.ptr.glsl_version.clone()
2
    }
    /// Soft-brush shader program for the GPU painting API (0 if unusable).
16
    #[must_use] pub fn get_brush_shader(&self) -> GLuint {
16
        self.ptr.brush_shader
16
    }
2
    #[must_use] pub fn get_fxaa_shader(&self) -> GLuint {
2
        self.ptr.fxaa_shader
2
    }
}
#[repr(C)]
pub struct GlContextPtrInner {
    pub ptr: Rc<GenericGlContext>,
    /// SVG shader program (library-internal use)
    pub svg_shader: GLuint,
    /// SVG multicolor shader program (library-internal use)
    pub svg_multicolor_shader: GLuint,
    /// FXAA shader program (library-internal use)
    pub fxaa_shader: GLuint,
    /// Soft-brush shader program for the GPU painting API (0 if unusable).
    pub brush_shader: GLuint,
    /// The GLSL `#version` directive that compiled (e.g. "150" or "300 es"),
    /// discovered by the probe in `new()`. Empty if the context is unusable.
    pub glsl_version: AzString,
}
impl fmt::Debug for GlContextPtrInner {
    // `ptr` wraps the external GL context (not Debug); show the rest.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("GlContextPtrInner")
            .field("svg_shader", &self.svg_shader)
            .field("svg_multicolor_shader", &self.svg_multicolor_shader)
            .field("fxaa_shader", &self.fxaa_shader)
            .field("brush_shader", &self.brush_shader)
            .field("glsl_version", &self.glsl_version)
            .finish_non_exhaustive()
    }
}
impl Drop for GlContextPtrInner {
207
    fn drop(&mut self) {
207
        self.ptr.delete_program(self.svg_shader);
207
        self.ptr.delete_program(self.svg_multicolor_shader);
207
        self.ptr.delete_program(self.fxaa_shader);
207
        if self.brush_shader != 0 {
            self.ptr.delete_program(self.brush_shader);
207
        }
207
    }
}
impl_option!(
    GlContextPtr,
    OptionGlContextPtr,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord]
);
impl fmt::Debug for GlContextPtr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "0x{:0x}", self.as_usize())
    }
}
static SVG_VERTEX_SHADER: &[u8] = b"#version 150
#if __VERSION__ != 100
    #define varying out
    #define attribute in
#endif
uniform vec2 vBboxSize;
uniform mat4 vTransformMatrix;
attribute vec2 vAttrXY;
void main() {
    vec4 vTransposed = vec4(vAttrXY, 1.0, 1.0) * vTransformMatrix;
    vec2 vTransposedInScreen = vTransposed.xy / vBboxSize;
    vec2 vCalcFinal = (vTransposedInScreen * vec2(2.0)) - vec2(1.0);
    gl_Position = vec4(vCalcFinal, 1.0, 1.0);
}";
static SVG_FRAGMENT_SHADER: &[u8] = b"#version 150
precision highp float;
uniform vec4 fDrawColor;
#if __VERSION__ == 100
    #define oFragColor gl_FragColor
#else
    out vec4 oFragColor;
#endif
void main() {
    oFragColor = fDrawColor;
}";
static SVG_MULTICOLOR_VERTEX_SHADER: &[u8] = b"#version 150
#if __VERSION__ != 100
    #define varying out
    #define attribute in
#endif
uniform vec2 vBboxSize;
uniform mat4 vTransformMatrix;
attribute vec3 vAttrXY;
attribute vec4 vColor;
varying vec4 fColor;
void main() {
    vec4 vTransposed = vec4(vAttrXY.xy, 1.0, 1.0) * vTransformMatrix;
    vec2 vTransposedInScreen = vTransposed.xy / vBboxSize;
    vec2 vCalcFinal = (vTransposedInScreen * vec2(2.0)) - vec2(1.0);
    gl_Position = vec4(vCalcFinal, vAttrXY.z, 1.0);
    fColor = vColor;
}";
static SVG_MULTICOLOR_FRAGMENT_SHADER: &[u8] = b"#version 150
precision highp float;
#if __VERSION__ != 100
    #define varying in
#endif
#if __VERSION__ == 100
    #define oFragColor gl_FragColor
#else
    out vec4 oFragColor;
#endif
varying vec4 fColor;
void main() {
    oFragColor = fColor;
}";
// Soft-brush shaders for the GPU painting API. A unit quad [-1,1]^2 (aUv) is
// positioned in NDC (aPos); the fragment computes the same radial falloff as
// the CPU `brush_dab_coverage` (1 - smoothstep(hardness, 1, dist)) so GPU and
// CPU strokes match. Version-agnostic via `__VERSION__` like the SVG shaders.
static BRUSH_VERTEX_SHADER: &[u8] = b"#version 150
#if __VERSION__ != 100
    #define varying out
    #define attribute in
#endif
attribute vec2 aPos;
attribute vec2 aUv;
varying vec2 vUv;
void main() {
    vUv = aUv;
    gl_Position = vec4(aPos, 0.0, 1.0);
}";
static BRUSH_FRAGMENT_SHADER: &[u8] = b"#version 150
precision highp float;
#if __VERSION__ != 100
    #define varying in
#endif
#if __VERSION__ == 100
    #define oFragColor gl_FragColor
#else
    out vec4 oFragColor;
#endif
uniform vec4 uColor;     // rgb + alpha (alpha already folds in flow * color.a)
uniform float uHardness; // 0 = soft .. 1 = hard edge
varying vec2 vUv;
void main() {
    float d = length(vUv);
    if (d > 1.0) { discard; }
    float edge0 = clamp(uHardness, 0.0, 1.0);
    float x = clamp((d - edge0) / max(1.0 - edge0, 1.0e-4), 0.0, 1.0);
    float cov = 1.0 - (x * x * (3.0 - 2.0 * x));
    oFragColor = vec4(uColor.rgb, uColor.a * cov);
}";
/// Checks if a shader compiled successfully. Logs an error under `std`.
/// (Retained for diagnostics; the version probe in `GlContextPtr::new` now does
/// its own status checks.)
#[allow(dead_code)]
#[allow(clippy::used_underscore_binding)] // intentional `_`-prefix (FFI/api.json pub field, or cfg-gated binding); access is deliberate
#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
fn check_shader_compile(gl_context: &GenericGlContext, shader: GLuint, _label: &str) {
    let mut status = [0_i32];
    unsafe { gl_context.get_shader_iv(shader, gl::COMPILE_STATUS, &mut status) };
    if status[0] != gl::TRUE as i32 {
        #[cfg(feature = "std")]
        {
            let log = gl_context.get_shader_info_log(shader);
            eprintln!("azul: {_label} shader compile error: {log}");
        }
    }
}
/// Checks if a program linked successfully. Logs an error under `std`.
#[allow(dead_code)]
#[allow(clippy::used_underscore_binding)] // intentional `_`-prefix (FFI/api.json pub field, or cfg-gated binding); access is deliberate
#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
fn check_program_link(gl_context: &GenericGlContext, program: GLuint, _label: &str) {
    let mut status = [0_i32];
    unsafe { gl_context.get_program_iv(program, gl::LINK_STATUS, &mut status) };
    if status[0] != gl::TRUE as i32 {
        #[cfg(feature = "std")]
        {
            let log = gl_context.get_program_info_log(program);
            eprintln!("azul: {_label} program link error: {log}");
        }
    }
}
/// Swap the leading `#version ...` line of a bundled shader for `version_line`
/// (which must include the trailing newline). The shader bodies branch on
/// `__VERSION__`, so only the directive needs to change between GL and GLES.
#[cfg(feature = "std")]
15
fn shader_with_glsl_version(src: &[u8], version_line: &[u8]) -> Vec<u8> {
147
    let body_start = src.iter().position(|&b| b == b'\n').map_or(0, |i| i + 1);
15
    let mut out = Vec::with_capacity(version_line.len() + src.len() - body_start);
15
    out.extend_from_slice(version_line);
15
    out.extend_from_slice(&src[body_start..]);
15
    out
15
}
/// Try to compile+link a vertex+fragment program at a specific GLSL `#version`.
/// Returns the linked program id, or `None` (after cleanup) on ANY compile or
/// link failure. This is how we PROVE a GL context is actually usable and which
/// `#version` its driver accepts -- creating a context can succeed yet leave it
/// unable to compile our shaders (broken driver, or a GLES context that rejects
/// the desktop `#version 150`).
#[cfg(feature = "std")]
// OpenGL binding: gl::* enum constants passed to the gl API as GLint/GLenum.
#[allow(clippy::cast_possible_wrap)]
3
fn try_compile_program(
3
    gl_context: &GenericGlContext,
3
    vert_src: &[u8],
3
    frag_src: &[u8],
3
    version_line: &[u8],
3
    attribs: &[(u32, &str)],
3
) -> Option<GLuint> {
3
    let vs = gl_context.create_shader(gl::VERTEX_SHADER);
3
    gl_context.shader_source(vs, &[shader_with_glsl_version(vert_src, version_line).as_slice()]);
3
    gl_context.compile_shader(vs);
3
    let fs = gl_context.create_shader(gl::FRAGMENT_SHADER);
3
    gl_context.shader_source(fs, &[shader_with_glsl_version(frag_src, version_line).as_slice()]);
3
    gl_context.compile_shader(fs);
3
    let mut s = [0_i32];
3
    unsafe { gl_context.get_shader_iv(vs, gl::COMPILE_STATUS, &mut s) };
3
    let vs_ok = s[0] == gl::TRUE as i32;
3
    unsafe { gl_context.get_shader_iv(fs, gl::COMPILE_STATUS, &mut s) };
3
    let fs_ok = s[0] == gl::TRUE as i32;
3
    if !vs_ok || !fs_ok {
3
        gl_context.delete_shader(vs);
3
        gl_context.delete_shader(fs);
3
        return None;
    }
    let prog = gl_context.create_program();
    gl_context.attach_shader(prog, vs);
    gl_context.attach_shader(prog, fs);
    for (loc, name) in attribs {
        gl_context.bind_attrib_location(prog, *loc, name);
    }
    gl_context.link_program(prog);
    gl_context.delete_shader(vs);
    gl_context.delete_shader(fs);
    let mut l = [0_i32];
    unsafe { gl_context.get_program_iv(prog, gl::LINK_STATUS, &mut l) };
    if l[0] == gl::TRUE as i32 {
        Some(prog)
    } else {
        gl_context.delete_program(prog);
        None
    }
3
}
/// GLSL `#version` directives to try, in preference order, per context type.
/// The first that compiles+links the SVG shaders is used for every program.
9
const fn glsl_version_candidates(gl_type: GlType) -> &'static [&'static [u8]] {
9
    match gl_type {
4
        GlType::Gl => &[b"#version 150\n", b"#version 330\n", b"#version 140\n"],
5
        GlType::Gles => &[b"#version 300 es\n", b"#version 100\n"],
    }
9
}
impl GlContextPtr {
207
    #[must_use] pub fn new(renderer_type: RendererType, gl_context: Rc<GenericGlContext>) -> Self {
        // Only attempt the SVG/FXAA GL shaders for a real GPU. In Software/CPU
        // mode nothing composites through them.
        //
        // PROVE the context is usable rather than trusting context creation: try
        // compiling the SVG program at each candidate `#version` for this context
        // type (desktop GL 1.50/3.30/1.40, or GLES 3.00/1.00) and use the first
        // that compiles+links for ALL programs. A GLES GPU (mobile) rejects the
        // desktop `#version 150`, and a broken driver rejects everything -- in the
        // latter case all program IDs stay 0 and `is_gl_usable()` returns false so
        // the window can fall back to CPU rendering.
        #[cfg(feature = "std")]
207
        let (svg_program_id, svg_multicolor_program_id, fxaa_program_id, brush_program_id, glsl_version) =
207
            if matches!(renderer_type, RendererType::Hardware) {
                use crate::gl_fxaa::{FXAA_FRAGMENT_SHADER, FXAA_VERTEX_SHADER};
1
                let gl_type: GlType = gl_context.get_type().into();
                // Probe via the SVG program; the first version that links wins.
1
                let mut svg = 0;
1
                let mut chosen: Option<&'static [u8]> = None;
3
                for ver in glsl_version_candidates(gl_type) {
3
                    if let Some(p) = try_compile_program(
3
                        &gl_context, SVG_VERTEX_SHADER, SVG_FRAGMENT_SHADER, ver, &[(0, "vAttrXY")],
3
                    ) {
                        svg = p;
                        chosen = Some(ver);
                        break;
3
                    }
                }
1
                chosen.map_or_else(|| {
1
                    eprintln!(
1
                        "azul: GL context UNUSABLE -- no GLSL version ({gl_type:?}) compiled the SVG \
1
                         shaders; the window should fall back to CPU rendering (is_gl_usable()=false)"
                    );
1
                    (0, 0, 0, 0, AzString::from_const_str(""))
1
                }, |ver| {
                    // "150" / "300 es": the directive minus "#version " and newline.
                    let ver_str: AzString = core::str::from_utf8(ver)
                        .unwrap_or("")
                        .trim()
                        .trim_start_matches("#version ")
                        .into();
                    eprintln!(
                        "azul: GL usable -- shaders compiled at GLSL {} ({:?})",
                        ver_str.as_str(),
                        gl_type
                    );
                    let mc = try_compile_program(
                        &gl_context, SVG_MULTICOLOR_VERTEX_SHADER, SVG_MULTICOLOR_FRAGMENT_SHADER,
                        ver, &[(0, "vAttrXY"), (1, "vColor")],
                    ).unwrap_or(0);
                    let fxaa = try_compile_program(
                        &gl_context, FXAA_VERTEX_SHADER, FXAA_FRAGMENT_SHADER, ver, &[(0, "vAttrXY")],
                    ).unwrap_or(0);
                    let brush = try_compile_program(
                        &gl_context, BRUSH_VERTEX_SHADER, BRUSH_FRAGMENT_SHADER, ver,
                        &[(0, "aPos"), (1, "aUv")],
                    ).unwrap_or(0);
                    (svg, mc, fxaa, brush, ver_str)
                })
            } else {
206
                (0, 0, 0, 0, AzString::from_const_str(""))
            };
        // no_std build keeps the original behavior (no probe / no shaders).
        #[cfg(not(feature = "std"))]
        let (svg_program_id, svg_multicolor_program_id, fxaa_program_id, brush_program_id, glsl_version) =
            (0u32, 0u32, 0u32, 0u32, AzString::from_const_str(""));
207
        Self {
207
            ptr: ManuallyDrop::new(Box::new(Rc::new(GlContextPtrInner {
207
                svg_shader: svg_program_id,
207
                svg_multicolor_shader: svg_multicolor_program_id,
207
                fxaa_shader: fxaa_program_id,
207
                brush_shader: brush_program_id,
207
                glsl_version,
207
                ptr: gl_context,
207
            }))),
207
            renderer_type,
207
            run_destructor: true,
207
        }
207
    }
434
    #[must_use] pub fn get(&self) -> &Rc<GenericGlContext> {
434
        &self.ptr.ptr
434
    }
18
    fn as_usize(&self) -> usize {
18
        (Rc::as_ptr(&self.ptr.ptr) as *const c_void) as usize
18
    }
}
// This impl is the OpenGL API wrapper: every method mirrors a C/gleam GL call and
// takes the C-ABI argument types (GlVoidPtrConst, *VecRef, …) BY VALUE to match that
// ABI/FFI calling convention. Switching them to references would break the contract,
// so needless_pass_by_value is allowed for the whole GL-binding impl.
#[allow(clippy::needless_pass_by_value)]
impl GlContextPtr {
1
    #[must_use] pub fn get_type(&self) -> GlType {
1
        self.get().get_type().into()
1
    }
1
    pub fn buffer_data_untyped(
1
        &self,
1
        target: GLenum,
1
        size: GLsizeiptr,
1
        data: GlVoidPtrConst,
1
        usage: GLenum,
1
    ) {
1
        self.get()
1
            .buffer_data_untyped(target, size, data.ptr, usage);
1
    }
16
    pub fn buffer_sub_data_untyped(
16
        &self,
16
        target: GLenum,
16
        offset: isize,
16
        size: GLsizeiptr,
16
        data: GlVoidPtrConst,
16
    ) {
16
        self.get()
16
            .buffer_sub_data_untyped(target, offset, size, data.ptr);
16
    }
    #[must_use] pub fn map_buffer(&self, target: GLenum, access: GLbitfield) -> GlVoidPtrMut {
        GlVoidPtrMut {
            ptr: self.get().map_buffer(target, access),
        }
    }
16
    #[must_use] pub fn map_buffer_range(
16
        &self,
16
        target: GLenum,
16
        offset: GLintptr,
16
        length: GLsizeiptr,
16
        access: GLbitfield,
16
    ) -> GlVoidPtrMut {
16
        GlVoidPtrMut {
16
            ptr: self.get().map_buffer_range(target, offset, length, access),
16
        }
16
    }
1
    #[must_use] pub fn unmap_buffer(&self, target: GLenum) -> GLboolean {
1
        self.get().unmap_buffer(target)
1
    }
    pub fn tex_buffer(&self, target: GLenum, internal_format: GLenum, buffer: GLuint) {
        self.get().tex_buffer(target, internal_format, buffer);
    }
2
    pub fn shader_source(&self, shader: GLuint, strings: StringVec) {
4
        fn str_to_bytes(input: &str) -> Vec<u8> {
4
            let mut v: Vec<u8> = input.into();
4
            v.push(0);
4
            v
4
        }
2
        let shaders_as_bytes = strings
2
            .iter()
4
            .map(|s| str_to_bytes(s.as_str()))
2
            .collect::<Vec<_>>();
2
        let shaders_as_bytes = shaders_as_bytes
2
            .iter()
2
            .map(AsRef::as_ref)
2
            .collect::<Vec<_>>();
2
        self.get().shader_source(shader, &shaders_as_bytes);
2
    }
    pub fn read_buffer(&self, mode: GLenum) {
        self.get().read_buffer(mode);
    }
2
    pub fn read_pixels_into_buffer(
2
        &self,
2
        x: GLint,
2
        y: GLint,
2
        width: GLsizei,
2
        height: GLsizei,
2
        format: GLenum,
2
        pixel_type: GLenum,
2
        mut dst_buffer: U8VecRefMut,
2
    ) {
2
        self.get().read_pixels_into_buffer(
2
            x,
2
            y,
2
            width,
2
            height,
2
            format,
2
            pixel_type,
2
            dst_buffer.as_mut_slice(),
2
        );
2
    }
2
    #[must_use] pub fn read_pixels(
2
        &self,
2
        x: GLint,
2
        y: GLint,
2
        width: GLsizei,
2
        height: GLsizei,
2
        format: GLenum,
2
        pixel_type: GLenum,
2
    ) -> U8Vec {
        // gl-context-loader's own read_pixels sizes the buffer as
        // width*height*bytes_per_component and OMITS the format's channel count, so a
        // 2x3 RGBA/UNSIGNED_BYTE read allocates 6 bytes instead of 24 — a heap overflow
        // once a real driver writes the pixels. Size it correctly from format + type and
        // read into our own buffer. (Raw GL enum values so this doesn't depend on which
        // constants the gl module happens to re-export.)
2
        let channels: usize = match format {
            // RED, ALPHA, LUMINANCE, DEPTH_COMPONENT, STENCIL_INDEX, RED_INTEGER
            0x1903 | 0x1906 | 0x1909 | 0x1902 | 0x1901 | 0x8D94 => 1,
            // RG, LUMINANCE_ALPHA, RG_INTEGER, DEPTH_STENCIL
            0x8227 | 0x190A | 0x8228 | 0x84F9 => 2,
            // RGB, BGR, RGB_INTEGER
            0x1907 | 0x80E0 | 0x8D98 => 3,
            // RGBA, BGRA, RGBA_INTEGER, and a conservative default
2
            _ => 4,
        };
2
        let bytes_per_component: usize = match pixel_type {
2
            0x1400 | 0x1401 => 1,          // BYTE, UNSIGNED_BYTE
            0x1402 | 0x1403 | 0x140B => 2, // SHORT, UNSIGNED_SHORT, HALF_FLOAT
            _ => 4,                        // INT, UNSIGNED_INT, FLOAT, and default
        };
        // width/height are clamped to >= 0 before the cast, so no sign is lost.
        #[allow(clippy::cast_sign_loss)]
2
        let len = (width.max(0) as usize)
2
            .saturating_mul(height.max(0) as usize)
2
            .saturating_mul(channels)
2
            .saturating_mul(bytes_per_component);
2
        let mut buf = vec![0u8; len];
2
        self.get().read_pixels_into_buffer(
2
            x,
2
            y,
2
            width,
2
            height,
2
            format,
2
            pixel_type,
2
            buf.as_mut_slice(),
2
        );
2
        buf.into()
2
    }
    pub fn read_pixels_into_pbo(
        &self,
        x: GLint,
        y: GLint,
        width: GLsizei,
        height: GLsizei,
        format: GLenum,
        pixel_type: GLenum,
    ) {
        unsafe {
            self.get()
                .read_pixels_into_pbo(x, y, width, height, format, pixel_type);
        }
    }
16
    pub fn sample_coverage(&self, value: GLclampf, invert: bool) {
16
        self.get().sample_coverage(value, invert);
16
    }
8
    pub fn polygon_offset(&self, factor: GLfloat, units: GLfloat) {
8
        self.get().polygon_offset(factor, units);
8
    }
2
    pub fn pixel_store_i(&self, name: GLenum, param: GLint) {
2
        self.get().pixel_store_i(name, param);
2
    }
5
    #[must_use] pub fn gen_buffers(&self, n: GLsizei) -> GLuintVec {
5
        self.get().gen_buffers(n).into()
5
    }
5
    #[must_use] pub fn gen_renderbuffers(&self, n: GLsizei) -> GLuintVec {
5
        self.get().gen_renderbuffers(n).into()
5
    }
7
    #[must_use] pub fn gen_framebuffers(&self, n: GLsizei) -> GLuintVec {
7
        self.get().gen_framebuffers(n).into()
7
    }
5
    #[must_use] pub fn gen_textures(&self, n: GLsizei) -> GLuintVec {
5
        self.get().gen_textures(n).into()
5
    }
6
    #[must_use] pub fn gen_vertex_arrays(&self, n: GLsizei) -> GLuintVec {
6
        self.get().gen_vertex_arrays(n).into()
6
    }
5
    #[must_use] pub fn gen_queries(&self, n: GLsizei) -> GLuintVec {
5
        self.get().gen_queries(n).into()
5
    }
    pub fn begin_query(&self, target: GLenum, id: GLuint) {
        self.get().begin_query(target, id);
    }
    pub fn end_query(&self, target: GLenum) {
        self.get().end_query(target);
    }
    pub fn query_counter(&self, id: GLuint, target: GLenum) {
        self.get().query_counter(id, target);
    }
    #[must_use] pub fn get_query_object_iv(&self, id: GLuint, pname: GLenum) -> i32 {
        self.get().get_query_object_iv(id, pname)
    }
    #[must_use] pub fn get_query_object_uiv(&self, id: GLuint, pname: GLenum) -> u32 {
        self.get().get_query_object_uiv(id, pname)
    }
    #[must_use] pub fn get_query_object_i64v(&self, id: GLuint, pname: GLenum) -> i64 {
        self.get().get_query_object_i64v(id, pname)
    }
    #[must_use] pub fn get_query_object_ui64v(&self, id: GLuint, pname: GLenum) -> u64 {
        self.get().get_query_object_ui64v(id, pname)
    }
2
    pub fn delete_queries(&self, queries: GLuintVecRef) {
2
        self.get().delete_queries(queries.as_slice());
2
    }
5
    pub fn delete_vertex_arrays(&self, vertex_arrays: GLuintVecRef) {
5
        self.get().delete_vertex_arrays(vertex_arrays.as_slice());
5
    }
5
    pub fn delete_buffers(&self, buffers: GLuintVecRef) {
5
        self.get().delete_buffers(buffers.as_slice());
5
    }
2
    pub fn delete_renderbuffers(&self, renderbuffers: GLuintVecRef) {
2
        self.get().delete_renderbuffers(renderbuffers.as_slice());
2
    }
2
    pub fn delete_framebuffers(&self, framebuffers: GLuintVecRef) {
2
        self.get().delete_framebuffers(framebuffers.as_slice());
2
    }
25
    pub fn delete_textures(&self, textures: GLuintVecRef) {
25
        self.get().delete_textures(textures.as_slice());
25
    }
    pub fn framebuffer_renderbuffer(
        &self,
        target: GLenum,
        attachment: GLenum,
        renderbuffertarget: GLenum,
        renderbuffer: GLuint,
    ) {
        self.get()
            .framebuffer_renderbuffer(target, attachment, renderbuffertarget, renderbuffer);
    }
    pub fn renderbuffer_storage(
        &self,
        target: GLenum,
        internalformat: GLenum,
        width: GLsizei,
        height: GLsizei,
    ) {
        self.get()
            .renderbuffer_storage(target, internalformat, width, height);
    }
    pub fn depth_func(&self, func: GLenum) {
        self.get().depth_func(func);
    }
    pub fn active_texture(&self, texture: GLenum) {
        self.get().active_texture(texture);
    }
    pub fn attach_shader(&self, program: GLuint, shader: GLuint) {
        self.get().attach_shader(program, shader);
    }
    pub fn bind_attrib_location(&self, program: GLuint, index: GLuint, name: &str) {
        self.get()
            .bind_attrib_location(program, index, name);
    }
2
    pub fn get_uniform_iv(&self, program: GLuint, location: GLint, mut result: GLintVecRefMut) {
2
        unsafe {
2
            self.get()
2
                .get_uniform_iv(program, location, result.as_mut_slice());
2
        }
2
    }
2
    pub fn get_uniform_fv(&self, program: GLuint, location: GLint, mut result: GLfloatVecRefMut) {
2
        unsafe {
2
            self.get()
2
                .get_uniform_fv(program, location, result.as_mut_slice());
2
        }
2
    }
    #[must_use] pub fn get_uniform_block_index(&self, program: GLuint, name: &str) -> GLuint {
        self.get().get_uniform_block_index(program, name)
    }
2
    #[must_use] pub fn get_uniform_indices(&self, program: GLuint, names: RefstrVecRef) -> GLuintVec {
2
        let names_vec = names
2
            .as_slice()
2
            .iter()
2
            .map(Refstr::as_str)
2
            .collect::<Vec<_>>();
2
        self.get().get_uniform_indices(program, &names_vec).into()
2
    }
    pub fn bind_buffer_base(&self, target: GLenum, index: GLuint, buffer: GLuint) {
        self.get().bind_buffer_base(target, index, buffer);
    }
    pub fn bind_buffer_range(
        &self,
        target: GLenum,
        index: GLuint,
        buffer: GLuint,
        offset: GLintptr,
        size: GLsizeiptr,
    ) {
        self.get()
            .bind_buffer_range(target, index, buffer, offset, size);
    }
    pub fn uniform_block_binding(
        &self,
        program: GLuint,
        uniform_block_index: GLuint,
        uniform_block_binding: GLuint,
    ) {
        self.get()
            .uniform_block_binding(program, uniform_block_index, uniform_block_binding);
    }
    pub fn bind_buffer(&self, target: GLenum, buffer: GLuint) {
        self.get().bind_buffer(target, buffer);
    }
    pub fn bind_vertex_array(&self, vao: GLuint) {
        self.get().bind_vertex_array(vao);
    }
    pub fn bind_renderbuffer(&self, target: GLenum, renderbuffer: GLuint) {
        self.get().bind_renderbuffer(target, renderbuffer);
    }
    pub fn bind_framebuffer(&self, target: GLenum, framebuffer: GLuint) {
        self.get().bind_framebuffer(target, framebuffer);
    }
132
    pub fn bind_texture(&self, target: GLenum, texture: GLuint) {
132
        self.get().bind_texture(target, texture);
132
    }
2
    pub fn draw_buffers(&self, bufs: GLenumVecRef) {
2
        self.get().draw_buffers(bufs.as_slice());
2
    }
132
    pub fn tex_image_2d(
132
        &self,
132
        target: GLenum,
132
        level: GLint,
132
        internal_format: GLint,
132
        width: GLsizei,
132
        height: GLsizei,
132
        border: GLint,
132
        format: GLenum,
132
        ty: GLenum,
132
        opt_data: OptionU8VecRef,
132
    ) {
132
        let opt_data = opt_data.as_option();
132
        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
132
        self.get().tex_image_2d(
132
            target,
132
            level,
132
            internal_format,
132
            width,
132
            height,
132
            border,
132
            format,
132
            ty,
132
            opt_data,
132
        );
132
    }
    pub fn compressed_tex_image_2d(
        &self,
        target: GLenum,
        level: GLint,
        internal_format: GLenum,
        width: GLsizei,
        height: GLsizei,
        border: GLint,
        data: U8VecRef,
    ) {
        self.get().compressed_tex_image_2d(
            target,
            level,
            internal_format,
            width,
            height,
            border,
            data.as_slice(),
        );
    }
    pub fn compressed_tex_sub_image_2d(
        &self,
        target: GLenum,
        level: GLint,
        xoffset: GLint,
        yoffset: GLint,
        width: GLsizei,
        height: GLsizei,
        format: GLenum,
        data: U8VecRef,
    ) {
        self.get().compressed_tex_sub_image_2d(
            target,
            level,
            xoffset,
            yoffset,
            width,
            height,
            format,
            data.as_slice(),
        );
    }
    pub fn tex_image_3d(
        &self,
        target: GLenum,
        level: GLint,
        internal_format: GLint,
        width: GLsizei,
        height: GLsizei,
        depth: GLsizei,
        border: GLint,
        format: GLenum,
        ty: GLenum,
        opt_data: OptionU8VecRef,
    ) {
        let opt_data = opt_data.as_option();
        let opt_data: Option<&[u8]> = opt_data.map(U8VecRef::as_slice);
        self.get().tex_image_3d(
            target,
            level,
            internal_format,
            width,
            height,
            depth,
            border,
            format,
            ty,
            opt_data,
        );
    }
    pub fn copy_tex_image_2d(
        &self,
        target: GLenum,
        level: GLint,
        internal_format: GLenum,
        x: GLint,
        y: GLint,
        width: GLsizei,
        height: GLsizei,
        border: GLint,
    ) {
        self.get()
            .copy_tex_image_2d(target, level, internal_format, x, y, width, height, border);
    }
    pub fn copy_tex_sub_image_2d(
        &self,
        target: GLenum,
        level: GLint,
        xoffset: GLint,
        yoffset: GLint,
        x: GLint,
        y: GLint,
        width: GLsizei,
        height: GLsizei,
    ) {
        self.get()
            .copy_tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height);
    }
    pub fn copy_tex_sub_image_3d(
        &self,
        target: GLenum,
        level: GLint,
        xoffset: GLint,
        yoffset: GLint,
        zoffset: GLint,
        x: GLint,
        y: GLint,
        width: GLsizei,
        height: GLsizei,
    ) {
        self.get().copy_tex_sub_image_3d(
            target, level, xoffset, yoffset, zoffset, x, y, width, height,
        );
    }
    pub fn tex_sub_image_2d(
        &self,
        target: GLenum,
        level: GLint,
        xoffset: GLint,
        yoffset: GLint,
        width: GLsizei,
        height: GLsizei,
        format: GLenum,
        ty: GLenum,
        data: U8VecRef,
    ) {
        self.get().tex_sub_image_2d(
            target,
            level,
            xoffset,
            yoffset,
            width,
            height,
            format,
            ty,
            data.as_slice(),
        );
    }
3
    pub fn tex_sub_image_2d_pbo(
3
        &self,
3
        target: GLenum,
3
        level: GLint,
3
        xoffset: GLint,
3
        yoffset: GLint,
3
        width: GLsizei,
3
        height: GLsizei,
3
        format: GLenum,
3
        ty: GLenum,
3
        offset: usize,
3
    ) {
3
        self.get().tex_sub_image_2d_pbo(
3
            target, level, xoffset, yoffset, width, height, format, ty, offset,
3
        );
3
    }
    pub fn tex_sub_image_3d(
        &self,
        target: GLenum,
        level: GLint,
        xoffset: GLint,
        yoffset: GLint,
        zoffset: GLint,
        width: GLsizei,
        height: GLsizei,
        depth: GLsizei,
        format: GLenum,
        ty: GLenum,
        data: U8VecRef,
    ) {
        self.get().tex_sub_image_3d(
            target,
            level,
            xoffset,
            yoffset,
            zoffset,
            width,
            height,
            depth,
            format,
            ty,
            data.as_slice(),
        );
    }
    pub fn tex_sub_image_3d_pbo(
        &self,
        target: GLenum,
        level: GLint,
        xoffset: GLint,
        yoffset: GLint,
        zoffset: GLint,
        width: GLsizei,
        height: GLsizei,
        depth: GLsizei,
        format: GLenum,
        ty: GLenum,
        offset: usize,
    ) {
        self.get().tex_sub_image_3d_pbo(
            target, level, xoffset, yoffset, zoffset, width, height, depth, format, ty, offset,
        );
    }
    pub fn tex_storage_2d(
        &self,
        target: GLenum,
        levels: GLint,
        internal_format: GLenum,
        width: GLsizei,
        height: GLsizei,
    ) {
        self.get()
            .tex_storage_2d(target, levels, internal_format, width, height);
    }
    pub fn tex_storage_3d(
        &self,
        target: GLenum,
        levels: GLint,
        internal_format: GLenum,
        width: GLsizei,
        height: GLsizei,
        depth: GLsizei,
    ) {
        self.get()
            .tex_storage_3d(target, levels, internal_format, width, height, depth);
    }
    pub fn get_tex_image_into_buffer(
        &self,
        target: GLenum,
        level: GLint,
        format: GLenum,
        ty: GLenum,
        mut output: U8VecRefMut,
    ) {
        self.get()
            .get_tex_image_into_buffer(target, level, format, ty, output.as_mut_slice());
    }
    pub fn copy_image_sub_data(
        &self,
        src_name: GLuint,
        src_target: GLenum,
        src_level: GLint,
        src_x: GLint,
        src_y: GLint,
        src_z: GLint,
        dst_name: GLuint,
        dst_target: GLenum,
        dst_level: GLint,
        dst_x: GLint,
        dst_y: GLint,
        dst_z: GLint,
        src_width: GLsizei,
        src_height: GLsizei,
        src_depth: GLsizei,
    ) {
        unsafe {
            self.get().copy_image_sub_data(
                src_name, src_target, src_level, src_x, src_y, src_z, dst_name, dst_target,
                dst_level, dst_x, dst_y, dst_z, src_width, src_height, src_depth,
            );
        }
    }
    pub fn invalidate_framebuffer(&self, target: GLenum, attachments: GLenumVecRef) {
        self.get()
            .invalidate_framebuffer(target, attachments.as_slice());
    }
    pub fn invalidate_sub_framebuffer(
        &self,
        target: GLenum,
        attachments: GLenumVecRef,
        xoffset: GLint,
        yoffset: GLint,
        width: GLsizei,
        height: GLsizei,
    ) {
        self.get().invalidate_sub_framebuffer(
            target,
            attachments.as_slice(),
            xoffset,
            yoffset,
            width,
            height,
        );
    }
8
    pub fn get_integer_v(&self, name: GLenum, mut result: GLintVecRefMut) {
8
        unsafe { self.get().get_integer_v(name, result.as_mut_slice()) }
8
    }
    pub fn get_integer_64v(&self, name: GLenum, mut result: GLint64VecRefMut) {
        unsafe { self.get().get_integer_64v(name, result.as_mut_slice()) }
    }
    pub fn get_integer_iv(&self, name: GLenum, index: GLuint, mut result: GLintVecRefMut) {
        unsafe {
            self.get()
                .get_integer_iv(name, index, result.as_mut_slice());
        }
    }
    pub fn get_integer_64iv(&self, name: GLenum, index: GLuint, mut result: GLint64VecRefMut) {
        unsafe {
            self.get()
                .get_integer_64iv(name, index, result.as_mut_slice());
        }
    }
6
    pub fn get_boolean_v(&self, name: GLenum, mut result: GLbooleanVecRefMut) {
6
        unsafe { self.get().get_boolean_v(name, result.as_mut_slice()) }
6
    }
    pub fn get_float_v(&self, name: GLenum, mut result: GLfloatVecRefMut) {
        unsafe { self.get().get_float_v(name, result.as_mut_slice()) }
    }
    #[must_use] pub fn get_framebuffer_attachment_parameter_iv(
        &self,
        target: GLenum,
        attachment: GLenum,
        pname: GLenum,
    ) -> GLint {
        self.get()
            .get_framebuffer_attachment_parameter_iv(target, attachment, pname)
    }
    #[must_use] pub fn get_renderbuffer_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint {
        self.get().get_renderbuffer_parameter_iv(target, pname)
    }
    #[must_use] pub fn get_tex_parameter_iv(&self, target: GLenum, name: GLenum) -> GLint {
        self.get().get_tex_parameter_iv(target, name)
    }
    #[must_use] pub fn get_tex_parameter_fv(&self, target: GLenum, name: GLenum) -> GLfloat {
        self.get().get_tex_parameter_fv(target, name)
    }
    pub fn tex_parameter_i(&self, target: GLenum, pname: GLenum, param: GLint) {
        self.get().tex_parameter_i(target, pname, param);
    }
    pub fn tex_parameter_f(&self, target: GLenum, pname: GLenum, param: GLfloat) {
        self.get().tex_parameter_f(target, pname, param);
    }
    pub fn framebuffer_texture_2d(
        &self,
        target: GLenum,
        attachment: GLenum,
        textarget: GLenum,
        texture: GLuint,
        level: GLint,
    ) {
        self.get()
            .framebuffer_texture_2d(target, attachment, textarget, texture, level);
    }
    pub fn framebuffer_texture_layer(
        &self,
        target: GLenum,
        attachment: GLenum,
        texture: GLuint,
        level: GLint,
        layer: GLint,
    ) {
        self.get()
            .framebuffer_texture_layer(target, attachment, texture, level, layer);
    }
    #[allow(clippy::similar_names)] // domain-standard coordinate/control-point names
    pub fn blit_framebuffer(
        &self,
        src_x0: GLint,
        src_y0: GLint,
        src_x1: GLint,
        src_y1: GLint,
        dst_x0: GLint,
        dst_y0: GLint,
        dst_x1: GLint,
        dst_y1: GLint,
        mask: GLbitfield,
        filter: GLenum,
    ) {
        self.get().blit_framebuffer(
            src_x0, src_y0, src_x1, src_y1, dst_x0, dst_y0, dst_x1, dst_y1, mask, filter,
        );
    }
    pub fn vertex_attrib_4f(&self, index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
        self.get().vertex_attrib_4f(index, x, y, z, w);
    }
    pub fn vertex_attrib_pointer_f32(
        &self,
        index: GLuint,
        size: GLint,
        normalized: bool,
        stride: GLsizei,
        offset: GLuint,
    ) {
        self.get()
            .vertex_attrib_pointer_f32(index, size, normalized, stride, offset);
    }
    pub fn vertex_attrib_pointer(
        &self,
        index: GLuint,
        size: GLint,
        type_: GLenum,
        normalized: bool,
        stride: GLsizei,
        offset: GLuint,
    ) {
        self.get()
            .vertex_attrib_pointer(index, size, type_, normalized, stride, offset);
    }
    pub fn vertex_attrib_i_pointer(
        &self,
        index: GLuint,
        size: GLint,
        type_: GLenum,
        stride: GLsizei,
        offset: GLuint,
    ) {
        self.get()
            .vertex_attrib_i_pointer(index, size, type_, stride, offset);
    }
    pub fn vertex_attrib_divisor(&self, index: GLuint, divisor: GLuint) {
        self.get().vertex_attrib_divisor(index, divisor);
    }
    pub fn viewport(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
        self.get().viewport(x, y, width, height);
    }
    pub fn scissor(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
        self.get().scissor(x, y, width, height);
    }
    pub fn line_width(&self, width: GLfloat) {
        self.get().line_width(width);
    }
    pub fn use_program(&self, program: GLuint) {
        self.get().use_program(program);
    }
    pub fn validate_program(&self, program: GLuint) {
        self.get().validate_program(program);
    }
    pub fn draw_arrays(&self, mode: GLenum, first: GLint, count: GLsizei) {
        self.get().draw_arrays(mode, first, count);
    }
    pub fn draw_arrays_instanced(
        &self,
        mode: GLenum,
        first: GLint,
        count: GLsizei,
        primcount: GLsizei,
    ) {
        self.get()
            .draw_arrays_instanced(mode, first, count, primcount);
    }
    pub fn draw_elements(
        &self,
        mode: GLenum,
        count: GLsizei,
        element_type: GLenum,
        indices_offset: GLuint,
    ) {
        self.get()
            .draw_elements(mode, count, element_type, indices_offset);
    }
    pub fn draw_elements_instanced(
        &self,
        mode: GLenum,
        count: GLsizei,
        element_type: GLenum,
        indices_offset: GLuint,
        primcount: GLsizei,
    ) {
        self.get()
            .draw_elements_instanced(mode, count, element_type, indices_offset, primcount);
    }
    pub fn blend_color(&self, r: f32, g: f32, b: f32, a: f32) {
        self.get().blend_color(r, g, b, a);
    }
    pub fn blend_func(&self, sfactor: GLenum, dfactor: GLenum) {
        self.get().blend_func(sfactor, dfactor);
    }
    pub fn blend_func_separate(
        &self,
        src_rgb: GLenum,
        dest_rgb: GLenum,
        src_alpha: GLenum,
        dest_alpha: GLenum,
    ) {
        self.get()
            .blend_func_separate(src_rgb, dest_rgb, src_alpha, dest_alpha);
    }
    pub fn blend_equation(&self, mode: GLenum) {
        self.get().blend_equation(mode);
    }
    pub fn blend_equation_separate(&self, mode_rgb: GLenum, mode_alpha: GLenum) {
        self.get().blend_equation_separate(mode_rgb, mode_alpha);
    }
    // mirrors glColorMask(GLboolean, GLboolean, GLboolean, GLboolean) — the four
    // RGBA write-mask flags are the GL API, not a refactorable bool soup.
    #[allow(clippy::fn_params_excessive_bools)]
    pub fn color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
        self.get().color_mask(r, g, b, a);
    }
    pub fn cull_face(&self, mode: GLenum) {
        self.get().cull_face(mode);
    }
    pub fn front_face(&self, mode: GLenum) {
        self.get().front_face(mode);
    }
    pub fn enable(&self, cap: GLenum) {
        self.get().enable(cap);
    }
    pub fn disable(&self, cap: GLenum) {
        self.get().disable(cap);
    }
    pub fn hint(&self, param_name: GLenum, param_val: GLenum) {
        self.get().hint(param_name, param_val);
    }
    #[must_use] pub fn is_enabled(&self, cap: GLenum) -> GLboolean {
        self.get().is_enabled(cap)
    }
    #[must_use] pub fn is_shader(&self, shader: GLuint) -> GLboolean {
        self.get().is_shader(shader)
    }
    #[must_use] pub fn is_texture(&self, texture: GLenum) -> GLboolean {
        self.get().is_texture(texture)
    }
    #[must_use] pub fn is_framebuffer(&self, framebuffer: GLenum) -> GLboolean {
        self.get().is_framebuffer(framebuffer)
    }
    #[must_use] pub fn is_renderbuffer(&self, renderbuffer: GLenum) -> GLboolean {
        self.get().is_renderbuffer(renderbuffer)
    }
    #[must_use] pub fn check_frame_buffer_status(&self, target: GLenum) -> GLenum {
        self.get().check_frame_buffer_status(target)
    }
    pub fn enable_vertex_attrib_array(&self, index: GLuint) {
        self.get().enable_vertex_attrib_array(index);
    }
    pub fn disable_vertex_attrib_array(&self, index: GLuint) {
        self.get().disable_vertex_attrib_array(index);
    }
    pub fn uniform_1f(&self, location: GLint, v0: GLfloat) {
        self.get().uniform_1f(location, v0);
    }
    pub fn uniform_1fv(&self, location: GLint, values: F32VecRef) {
        self.get().uniform_1fv(location, values.as_slice());
    }
    pub fn uniform_1i(&self, location: GLint, v0: GLint) {
        self.get().uniform_1i(location, v0);
    }
    pub fn uniform_1iv(&self, location: GLint, values: I32VecRef) {
        self.get().uniform_1iv(location, values.as_slice());
    }
    pub fn uniform_1ui(&self, location: GLint, v0: GLuint) {
        self.get().uniform_1ui(location, v0);
    }
    pub fn uniform_2f(&self, location: GLint, v0: GLfloat, v1: GLfloat) {
        self.get().uniform_2f(location, v0, v1);
    }
    pub fn uniform_2fv(&self, location: GLint, values: F32VecRef) {
        self.get().uniform_2fv(location, values.as_slice());
    }
    pub fn uniform_2i(&self, location: GLint, v0: GLint, v1: GLint) {
        self.get().uniform_2i(location, v0, v1);
    }
    pub fn uniform_2iv(&self, location: GLint, values: I32VecRef) {
        self.get().uniform_2iv(location, values.as_slice());
    }
    pub fn uniform_2ui(&self, location: GLint, v0: GLuint, v1: GLuint) {
        self.get().uniform_2ui(location, v0, v1);
    }
    pub fn uniform_3f(&self, location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat) {
        self.get().uniform_3f(location, v0, v1, v2);
    }
    pub fn uniform_3fv(&self, location: GLint, values: F32VecRef) {
        self.get().uniform_3fv(location, values.as_slice());
    }
    pub fn uniform_3i(&self, location: GLint, v0: GLint, v1: GLint, v2: GLint) {
        self.get().uniform_3i(location, v0, v1, v2);
    }
    pub fn uniform_3iv(&self, location: GLint, values: I32VecRef) {
        self.get().uniform_3iv(location, values.as_slice());
    }
    pub fn uniform_3ui(&self, location: GLint, v0: GLuint, v1: GLuint, v2: GLuint) {
        self.get().uniform_3ui(location, v0, v1, v2);
    }
    pub fn uniform_4f(&self, location: GLint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) {
        self.get().uniform_4f(location, x, y, z, w);
    }
    pub fn uniform_4i(&self, location: GLint, x: GLint, y: GLint, z: GLint, w: GLint) {
        self.get().uniform_4i(location, x, y, z, w);
    }
    pub fn uniform_4iv(&self, location: GLint, values: I32VecRef) {
        self.get().uniform_4iv(location, values.as_slice());
    }
    pub fn uniform_4ui(&self, location: GLint, x: GLuint, y: GLuint, z: GLuint, w: GLuint) {
        self.get().uniform_4ui(location, x, y, z, w);
    }
    pub fn uniform_4fv(&self, location: GLint, values: F32VecRef) {
        self.get().uniform_4fv(location, values.as_slice());
    }
    pub fn uniform_matrix_2fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
        self.get()
            .uniform_matrix_2fv(location, transpose, value.as_slice());
    }
    pub fn uniform_matrix_3fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
        self.get()
            .uniform_matrix_3fv(location, transpose, value.as_slice());
    }
    pub fn uniform_matrix_4fv(&self, location: GLint, transpose: bool, value: F32VecRef) {
        self.get()
            .uniform_matrix_4fv(location, transpose, value.as_slice());
    }
    pub fn depth_mask(&self, flag: bool) {
        self.get().depth_mask(flag);
    }
    pub fn depth_range(&self, near: f64, far: f64) {
        self.get().depth_range(near, far);
    }
    #[must_use] pub fn get_active_attrib(&self, program: GLuint, index: GLuint) -> GetActiveAttribReturn {
        let r = self.get().get_active_attrib(program, index);
        GetActiveAttribReturn {
            _0: r.0,
            _1: r.1,
            _2: r.2.into(),
        }
    }
    #[must_use] pub fn get_active_uniform(&self, program: GLuint, index: GLuint) -> GetActiveUniformReturn {
        let r = self.get().get_active_uniform(program, index);
        GetActiveUniformReturn {
            _0: r.0,
            _1: r.1,
            _2: r.2.into(),
        }
    }
    #[must_use] pub fn get_active_uniforms_iv(
        &self,
        program: GLuint,
        indices: GLuintVec,
        pname: GLenum,
    ) -> GLintVec {
        self.get()
            .get_active_uniforms_iv(program, indices.into_library_owned_vec(), pname)
            .into()
    }
    #[must_use] pub fn get_active_uniform_block_i(
        &self,
        program: GLuint,
        index: GLuint,
        pname: GLenum,
    ) -> GLint {
        self.get().get_active_uniform_block_i(program, index, pname)
    }
    #[must_use] pub fn get_active_uniform_block_iv(
        &self,
        program: GLuint,
        index: GLuint,
        pname: GLenum,
    ) -> GLintVec {
        self.get()
            .get_active_uniform_block_iv(program, index, pname)
            .into()
    }
    #[must_use] pub fn get_active_uniform_block_name(&self, program: GLuint, index: GLuint) -> AzString {
        self.get()
            .get_active_uniform_block_name(program, index)
            .into()
    }
    #[must_use] pub fn get_attrib_location(&self, program: GLuint, name: &str) -> c_int {
        self.get().get_attrib_location(program, name)
    }
    #[must_use] pub fn get_frag_data_location(&self, program: GLuint, name: &str) -> c_int {
        self.get().get_frag_data_location(program, name)
    }
    #[must_use] pub fn get_uniform_location(&self, program: GLuint, name: &str) -> c_int {
        self.get().get_uniform_location(program, name)
    }
    #[must_use] pub fn get_program_info_log(&self, program: GLuint) -> AzString {
        self.get().get_program_info_log(program).into()
    }
    pub fn get_program_iv(&self, program: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
        unsafe {
            self.get()
                .get_program_iv(program, pname, result.as_mut_slice());
        }
    }
    #[must_use] pub fn get_program_binary(&self, program: GLuint) -> GetProgramBinaryReturn {
        let r = self.get().get_program_binary(program);
        GetProgramBinaryReturn {
            _0: r.0.into(),
            _1: r.1,
        }
    }
    pub fn program_binary(&self, program: GLuint, format: GLenum, binary: U8VecRef) {
        self.get()
            .program_binary(program, format, binary.as_slice());
    }
    pub fn program_parameter_i(&self, program: GLuint, pname: GLenum, value: GLint) {
        self.get().program_parameter_i(program, pname, value);
    }
    pub fn get_vertex_attrib_iv(&self, index: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
        unsafe {
            self.get()
                .get_vertex_attrib_iv(index, pname, result.as_mut_slice());
        }
    }
    pub fn get_vertex_attrib_fv(&self, index: GLuint, pname: GLenum, mut result: GLfloatVecRefMut) {
        unsafe {
            self.get()
                .get_vertex_attrib_fv(index, pname, result.as_mut_slice());
        }
    }
    #[must_use] pub fn get_vertex_attrib_pointer_v(&self, index: GLuint, pname: GLenum) -> GLsizeiptr {
        self.get().get_vertex_attrib_pointer_v(index, pname)
    }
    #[must_use] pub fn get_buffer_parameter_iv(&self, target: GLuint, pname: GLenum) -> GLint {
        self.get().get_buffer_parameter_iv(target, pname)
    }
    #[must_use] pub fn get_shader_info_log(&self, shader: GLuint) -> AzString {
        self.get().get_shader_info_log(shader).into()
    }
    #[must_use] pub fn get_string(&self, which: GLenum) -> AzString {
        self.get().get_string(which).into()
    }
    #[must_use] pub fn get_string_i(&self, which: GLenum, index: GLuint) -> AzString {
        self.get().get_string_i(which, index).into()
    }
    pub fn get_shader_iv(&self, shader: GLuint, pname: GLenum, mut result: GLintVecRefMut) {
        unsafe {
            self.get()
                .get_shader_iv(shader, pname, result.as_mut_slice());
        }
    }
    #[must_use] pub fn get_shader_precision_format(
        &self,
        shader_type: GLuint,
        precision_type: GLuint,
    ) -> GlShaderPrecisionFormatReturn {
        let r = self
            .get()
            .get_shader_precision_format(shader_type, precision_type);
        GlShaderPrecisionFormatReturn {
            _0: r.0,
            _1: r.1,
            _2: r.2,
        }
    }
    pub fn compile_shader(&self, shader: GLuint) {
        self.get().compile_shader(shader);
    }
    #[must_use] pub fn create_program(&self) -> GLuint {
        self.get().create_program()
    }
    pub fn delete_program(&self, program: GLuint) {
        self.get().delete_program(program);
    }
    #[must_use] pub fn create_shader(&self, shader_type: GLenum) -> GLuint {
        self.get().create_shader(shader_type)
    }
    pub fn delete_shader(&self, shader: GLuint) {
        self.get().delete_shader(shader);
    }
    pub fn detach_shader(&self, program: GLuint, shader: GLuint) {
        self.get().detach_shader(program, shader);
    }
    pub fn link_program(&self, program: GLuint) {
        self.get().link_program(program);
    }
    pub fn clear_color(&self, r: f32, g: f32, b: f32, a: f32) {
        self.get().clear_color(r, g, b, a);
    }
    pub fn clear(&self, buffer_mask: GLbitfield) {
        self.get().clear(buffer_mask);
    }
    pub fn clear_depth(&self, depth: f64) {
        self.get().clear_depth(depth);
    }
    pub fn clear_stencil(&self, s: GLint) {
        self.get().clear_stencil(s);
    }
    pub fn flush(&self) {
        self.get().flush();
    }
    pub fn finish(&self) {
        self.get().finish();
    }
    #[must_use] pub fn get_error(&self) -> GLenum {
        self.get().get_error()
    }
    pub fn stencil_mask(&self, mask: GLuint) {
        self.get().stencil_mask(mask);
    }
    pub fn stencil_mask_separate(&self, face: GLenum, mask: GLuint) {
        self.get().stencil_mask_separate(face, mask);
    }
    pub fn stencil_func(&self, func: GLenum, ref_: GLint, mask: GLuint) {
        self.get().stencil_func(func, ref_, mask);
    }
    pub fn stencil_func_separate(&self, face: GLenum, func: GLenum, ref_: GLint, mask: GLuint) {
        self.get().stencil_func_separate(face, func, ref_, mask);
    }
    pub fn stencil_op(&self, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
        self.get().stencil_op(sfail, dpfail, dppass);
    }
    pub fn stencil_op_separate(&self, face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) {
        self.get().stencil_op_separate(face, sfail, dpfail, dppass);
    }
    pub fn egl_image_target_texture2d_oes(&self, target: GLenum, image: GlVoidPtrConst) {
        self.get()
            .egl_image_target_texture2d_oes(target, image.ptr as *const c_void);
    }
    pub fn generate_mipmap(&self, target: GLenum) {
        self.get().generate_mipmap(target);
    }
    pub fn insert_event_marker_ext(&self, message: &str) {
        self.get().insert_event_marker_ext(message);
    }
    pub fn push_group_marker_ext(&self, message: &str) {
        self.get().push_group_marker_ext(message);
    }
    pub fn pop_group_marker_ext(&self) {
        self.get().pop_group_marker_ext();
    }
    pub fn debug_message_insert_khr(
        &self,
        source: GLenum,
        type_: GLenum,
        id: GLuint,
        severity: GLenum,
        message: &str,
    ) {
        self.get()
            .debug_message_insert_khr(source, type_, id, severity, message);
    }
    pub fn push_debug_group_khr(&self, source: GLenum, id: GLuint, message: &str) {
        self.get()
            .push_debug_group_khr(source, id, message);
    }
    pub fn pop_debug_group_khr(&self) {
        self.get().pop_debug_group_khr();
    }
    #[must_use] pub fn fence_sync(&self, condition: GLenum, flags: GLbitfield) -> GLsyncPtr {
        GLsyncPtr::new(self.get().fence_sync(condition, flags))
    }
    #[must_use] pub fn client_wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) -> u32 {
        self.get().client_wait_sync(sync.get(), flags, timeout)
    }
    pub fn wait_sync(&self, sync: GLsyncPtr, flags: GLbitfield, timeout: GLuint64) {
        self.get().wait_sync(sync.get(), flags, timeout);
    }
    pub fn delete_sync(&self, sync: GLsyncPtr) {
        self.get().delete_sync(sync.get());
    }
    pub fn texture_range_apple(&self, target: GLenum, data: U8VecRef) {
        self.get().texture_range_apple(target, data.as_slice());
    }
    #[must_use] pub fn gen_fences_apple(&self, n: GLsizei) -> GLuintVec {
        self.get().gen_fences_apple(n).into()
    }
    pub fn delete_fences_apple(&self, fences: GLuintVecRef) {
        self.get().delete_fences_apple(fences.as_slice());
    }
    pub fn set_fence_apple(&self, fence: GLuint) {
        self.get().set_fence_apple(fence);
    }
    pub fn finish_fence_apple(&self, fence: GLuint) {
        self.get().finish_fence_apple(fence);
    }
    pub fn test_fence_apple(&self, fence: GLuint) {
        self.get().test_fence_apple(fence);
    }
    #[must_use] pub fn test_object_apple(&self, object: GLenum, name: GLuint) -> GLboolean {
        self.get().test_object_apple(object, name)
    }
    pub fn finish_object_apple(&self, object: GLenum, name: GLuint) {
        self.get().finish_object_apple(object, name);
    }
    #[must_use] pub fn get_frag_data_index(&self, program: GLuint, name: &str) -> GLint {
        self.get().get_frag_data_index(program, name)
    }
    pub fn blend_barrier_khr(&self) {
        self.get().blend_barrier_khr();
    }
    pub fn bind_frag_data_location_indexed(
        &self,
        program: GLuint,
        color_number: GLuint,
        index: GLuint,
        name: &str,
    ) {
        self.get()
            .bind_frag_data_location_indexed(program, color_number, index, name);
    }
    #[must_use] pub fn get_debug_messages(&self) -> DebugMessageVec {
        let dmv: Vec<DebugMessage> = self
            .get()
            .get_debug_messages()
            .into_iter()
            .map(|d| DebugMessage {
                message: d.message.into(),
                source: d.source,
                ty: d.ty,
                id: d.id,
                severity: d.severity,
            })
            .collect();
        dmv.into()
    }
    pub fn provoking_vertex_angle(&self, mode: GLenum) {
        self.get().provoking_vertex_angle(mode);
    }
    #[must_use] pub fn gen_vertex_arrays_apple(&self, n: GLsizei) -> GLuintVec {
        self.get().gen_vertex_arrays_apple(n).into()
    }
    pub fn bind_vertex_array_apple(&self, vao: GLuint) {
        self.get().bind_vertex_array_apple(vao);
    }
    pub fn delete_vertex_arrays_apple(&self, vertex_arrays: GLuintVecRef) {
        self.get()
            .delete_vertex_arrays_apple(vertex_arrays.as_slice());
    }
    pub fn copy_texture_chromium(
        &self,
        source_id: GLuint,
        source_level: GLint,
        dest_target: GLenum,
        dest_id: GLuint,
        dest_level: GLint,
        internal_format: GLint,
        dest_type: GLenum,
        unpack_flip_y: GLboolean,
        unpack_premultiply_alpha: GLboolean,
        unpack_unmultiply_alpha: GLboolean,
    ) {
        self.get().copy_texture_chromium(
            source_id,
            source_level,
            dest_target,
            dest_id,
            dest_level,
            internal_format,
            dest_type,
            unpack_flip_y,
            unpack_premultiply_alpha,
            unpack_unmultiply_alpha,
        );
    }
    pub fn copy_sub_texture_chromium(
        &self,
        source_id: GLuint,
        source_level: GLint,
        dest_target: GLenum,
        dest_id: GLuint,
        dest_level: GLint,
        x_offset: GLint,
        y_offset: GLint,
        x: GLint,
        y: GLint,
        width: GLsizei,
        height: GLsizei,
        unpack_flip_y: GLboolean,
        unpack_premultiply_alpha: GLboolean,
        unpack_unmultiply_alpha: GLboolean,
    ) {
        self.get().copy_sub_texture_chromium(
            source_id,
            source_level,
            dest_target,
            dest_id,
            dest_level,
            x_offset,
            y_offset,
            x,
            y,
            width,
            height,
            unpack_flip_y,
            unpack_premultiply_alpha,
            unpack_unmultiply_alpha,
        );
    }
    pub fn egl_image_target_renderbuffer_storage_oes(&self, target: u32, image: GlVoidPtrConst) {
        self.get().egl_image_target_renderbuffer_storage_oes(
            target,
            image.ptr as *const c_void,
        );
    }
    pub fn copy_texture_3d_angle(
        &self,
        source_id: GLuint,
        source_level: GLint,
        dest_target: GLenum,
        dest_id: GLuint,
        dest_level: GLint,
        internal_format: GLint,
        dest_type: GLenum,
        unpack_flip_y: GLboolean,
        unpack_premultiply_alpha: GLboolean,
        unpack_unmultiply_alpha: GLboolean,
    ) {
        self.get().copy_texture_3d_angle(
            source_id,
            source_level,
            dest_target,
            dest_id,
            dest_level,
            internal_format,
            dest_type,
            unpack_flip_y,
            unpack_premultiply_alpha,
            unpack_unmultiply_alpha,
        );
    }
    pub fn copy_sub_texture_3d_angle(
        &self,
        source_id: GLuint,
        source_level: GLint,
        dest_target: GLenum,
        dest_id: GLuint,
        dest_level: GLint,
        x_offset: GLint,
        y_offset: GLint,
        z_offset: GLint,
        x: GLint,
        y: GLint,
        z: GLint,
        width: GLsizei,
        height: GLsizei,
        depth: GLsizei,
        unpack_flip_y: GLboolean,
        unpack_premultiply_alpha: GLboolean,
        unpack_unmultiply_alpha: GLboolean,
    ) {
        self.get().copy_sub_texture_3d_angle(
            source_id,
            source_level,
            dest_target,
            dest_id,
            dest_level,
            x_offset,
            y_offset,
            z_offset,
            x,
            y,
            z,
            width,
            height,
            depth,
            unpack_flip_y,
            unpack_premultiply_alpha,
            unpack_unmultiply_alpha,
        );
    }
    pub fn buffer_storage(
        &self,
        target: GLenum,
        size: GLsizeiptr,
        data: GlVoidPtrConst,
        flags: GLbitfield,
    ) {
        self.get().buffer_storage(target, size, data.ptr, flags);
    }
    pub fn flush_mapped_buffer_range(&self, target: GLenum, offset: GLintptr, length: GLsizeiptr) {
        self.get().flush_mapped_buffer_range(target, offset, length);
    }
}
impl PartialEq for GlContextPtr {
3
    fn eq(&self, rhs: &Self) -> bool {
3
        self.as_usize().eq(&rhs.as_usize())
3
    }
}
impl Eq for GlContextPtr {}
impl PartialOrd for GlContextPtr {
2
    fn partial_cmp(&self, rhs: &Self) -> Option<core::cmp::Ordering> {
2
        self.as_usize().partial_cmp(&rhs.as_usize())
2
    }
}
impl Ord for GlContextPtr {
4
    fn cmp(&self, rhs: &Self) -> core::cmp::Ordering {
4
        self.as_usize().cmp(&rhs.as_usize())
4
    }
}
/// Saved OpenGL state for save/restore around framebuffer operations.
/// Used by `Texture::clear()` and `GlShader::draw()` to avoid corrupting
/// the caller's GL state.
// the `current_` prefix is intentional: each field holds the saved CURRENT GL
// binding captured at save() to be restored in restore().
#[allow(clippy::struct_field_names)]
struct GlStateSave {
    current_multisample: [u8; 1],
    current_index_buffer: [i32; 1],
    current_vertex_buffer: [i32; 1],
    current_vertex_array_object: [i32; 1],
    current_program: [i32; 1],
    current_framebuffers: [i32; 1],
    current_renderbuffers: [i32; 1],
    current_texture_2d: [i32; 1],
}
impl GlStateSave {
1
    fn save(gl_context: &GlContextPtr) -> Self {
1
        let mut s = Self {
1
            current_multisample: [0],
1
            current_index_buffer: [0],
1
            current_vertex_buffer: [0],
1
            current_vertex_array_object: [0],
1
            current_program: [0],
1
            current_framebuffers: [0],
1
            current_renderbuffers: [0],
1
            current_texture_2d: [0],
1
        };
1
        gl_context.get_boolean_v(gl::MULTISAMPLE, (&mut s.current_multisample[..]).into());
1
        gl_context.get_integer_v(gl::ARRAY_BUFFER_BINDING, (&mut s.current_vertex_buffer[..]).into());
1
        gl_context.get_integer_v(gl::ELEMENT_ARRAY_BUFFER_BINDING, (&mut s.current_index_buffer[..]).into());
1
        gl_context.get_integer_v(gl::CURRENT_PROGRAM, (&mut s.current_program[..]).into());
1
        gl_context.get_integer_v(gl::VERTEX_ARRAY_BINDING, (&mut s.current_vertex_array_object[..]).into());
1
        gl_context.get_integer_v(gl::RENDERBUFFER, (&mut s.current_renderbuffers[..]).into());
1
        gl_context.get_integer_v(gl::FRAMEBUFFER, (&mut s.current_framebuffers[..]).into());
1
        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut s.current_texture_2d[..]).into());
1
        s
1
    }
    // OpenGL binding: state values passed to the gl API as GLuint/GLsizei.
    #[allow(clippy::cast_sign_loss)]
    fn restore(&self, gl_context: &GlContextPtr) {
        if u32::from(self.current_multisample[0]) == gl::TRUE {
            gl_context.enable(gl::MULTISAMPLE);
        }
        gl_context.bind_framebuffer(gl::FRAMEBUFFER, self.current_framebuffers[0] as u32);
        gl_context.bind_texture(gl::TEXTURE_2D, self.current_texture_2d[0] as u32);
        gl_context.bind_buffer(gl::RENDERBUFFER, self.current_renderbuffers[0] as u32);
        gl_context.bind_vertex_array(self.current_vertex_array_object[0] as u32);
        gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.current_index_buffer[0] as u32);
        gl_context.bind_buffer(gl::ARRAY_BUFFER, self.current_vertex_buffer[0] as u32);
        gl_context.use_program(self.current_program[0] as u32);
    }
}
/// AUDIT: RAII guard that deletes a transient framebuffer + renderbuffer on
/// scope exit. Used by `Texture::clear` and `GlShader::draw` so that a panic
/// mid-path (e.g. an `.unwrap()` on an empty gen-list, or any GL step that
/// panics) can't leak the FBO/RBO. Ids of `0` are skipped (GL treats delete-0
/// as a no-op anyway, but this keeps intent explicit).
struct FboRboGuard<'a> {
    gl_context: &'a GlContextPtr,
    framebuffer_id: GLuint,
    renderbuffer_id: GLuint,
}
impl Drop for FboRboGuard<'_> {
    fn drop(&mut self) {
        if self.framebuffer_id != 0 {
            self.gl_context
                .delete_framebuffers((&[self.framebuffer_id])[..].into());
        }
        if self.renderbuffer_id != 0 {
            self.gl_context
                .delete_renderbuffers((&[self.renderbuffer_id])[..].into());
        }
    }
}
/// OpenGL texture, use `ReadOnlyWindow::create_texture` to create a texture
#[repr(C)]
pub struct Texture {
    /// A reference-counted pointer to the OpenGL context (so that the texture can be deleted in
    /// the destructor)
    pub gl_context: GlContextPtr,
    /// Raw OpenGL texture ID
    pub texture_id: GLuint,
    /// Reference count, shared across
    pub refcount: *const AtomicUsize,
    /// Size of this texture (in pixels)
    pub size: PhysicalSizeU32,
    /// Format of the texture (rgba8, brga8, etc.)
    pub format: RawImageFormat,
    /// Background color of this texture
    pub background_color: ColorU,
    /// Hints and flags for optimization purposes
    pub flags: TextureFlags,
    pub run_destructor: bool,
}
impl Clone for Texture {
    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
16
    fn clone(&self) -> Self {
16
        unsafe {
16
            (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst);
16
        }
16
        Self {
16
            gl_context: self.gl_context.clone(),
16
            texture_id: self.texture_id,
16
            refcount: self.refcount,
16
            size: self.size,
16
            format: self.format,
16
            background_color: self.background_color,
16
            flags: self.flags,
16
            run_destructor: true,
16
        }
16
    }
}
impl_option!(
    Texture,
    OptionTexture,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl Texture {
22
    #[must_use] pub fn create(
22
        texture_id: GLuint,
22
        flags: TextureFlags,
22
        size: PhysicalSizeU32,
22
        background_color: ColorU,
22
        gl_context: GlContextPtr,
22
        format: RawImageFormat,
22
    ) -> Self {
22
        Self {
22
            texture_id,
22
            flags,
22
            size,
22
            background_color,
22
            gl_context,
22
            format,
22
            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
22
            run_destructor: true,
22
        }
22
    }
    // OpenGL binding: gl::* enum constants and texture dimensions are passed as
    // GLint/GLsizei (i32); the values are GL-bounded and the `as i32` casts are the
    // idiomatic form for the gl API.
    #[allow(clippy::cast_possible_wrap)]
    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
    #[must_use] pub fn allocate_rgba8(
        gl_context: GlContextPtr,
        size: PhysicalSizeU32,
        background: ColorU,
    ) -> Self {
        let textures = gl_context.gen_textures(1);
        let texture_id = textures.as_ref()[0];
        let mut current_texture_2d = [0_i32];
        gl_context.get_integer_v(gl::TEXTURE_2D, (&mut current_texture_2d[..]).into());
        gl_context.bind_texture(gl::TEXTURE_2D, texture_id);
        gl_context.tex_image_2d(
            gl::TEXTURE_2D,
            0,
            gl::RGBA as i32,
            size.width as i32,
            size.height as i32,
            0,
            gl::RGBA,
            gl::UNSIGNED_BYTE,
            None.into(),
        );
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
        gl_context.bind_texture(gl::TEXTURE_2D, current_texture_2d[0] as u32);
        Self::create(
            texture_id,
            TextureFlags {
                is_opaque: false,
                is_video_texture: false,
            },
            size,
            background,
            gl_context,
            // Format is BGRA8 for WebRender integration, despite the GL upload using RGBA
            RawImageFormat::BGRA8,
        )
    }
    /// # Panics
    ///
    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
    // OpenGL binding: gl::* enum constants and texture dimensions passed as
    // GLint/GLsizei (i32); values are GL-bounded, `as i32` is the idiomatic form.
    #[allow(clippy::cast_possible_wrap)]
1
    pub fn clear(&mut self) {
1
        let saved = GlStateSave::save(&self.gl_context);
1
        let framebuffers = self.gl_context.gen_framebuffers(1);
1
        let framebuffer_id = *framebuffers.get(0).unwrap();
        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
        // panic in `gen_renderbuffers().get(0).unwrap()` can't leak it.
1
        let mut fbo_rbo_guard = FboRboGuard {
1
            gl_context: &self.gl_context,
1
            framebuffer_id,
1
            renderbuffer_id: 0,
1
        };
1
        self.gl_context
1
            .bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
1
        let depthbuffers = self.gl_context.gen_renderbuffers(1);
1
        let depthbuffer_id = *depthbuffers.get(0).unwrap();
1
        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
1
        self.gl_context
1
            .bind_texture(gl::TEXTURE_2D, self.texture_id);
1
        self.gl_context.tex_image_2d(
            gl::TEXTURE_2D,
            0,
1
            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
1
            self.size.width as i32,
1
            self.size.height as i32,
            0,
            gl::RGBA, // gl::BGRA?
            gl::UNSIGNED_BYTE,
1
            None.into(),
        );
1
        self.gl_context
1
            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
1
        self.gl_context
1
            .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
1
        self.gl_context.tex_parameter_i(
            gl::TEXTURE_2D,
            gl::TEXTURE_WRAP_S,
1
            gl::CLAMP_TO_EDGE as i32,
        );
1
        self.gl_context.tex_parameter_i(
            gl::TEXTURE_2D,
            gl::TEXTURE_WRAP_T,
1
            gl::CLAMP_TO_EDGE as i32,
        );
1
        self.gl_context
1
            .bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
1
        self.gl_context.renderbuffer_storage(
            gl::RENDERBUFFER,
            gl::DEPTH_COMPONENT,
1
            self.size.width as i32,
1
            self.size.height as i32,
        );
1
        self.gl_context.framebuffer_renderbuffer(
            gl::FRAMEBUFFER,
            gl::DEPTH_ATTACHMENT,
            gl::RENDERBUFFER,
1
            depthbuffer_id,
        );
1
        self.gl_context.framebuffer_texture_2d(
            gl::FRAMEBUFFER,
            gl::COLOR_ATTACHMENT0,
            gl::TEXTURE_2D,
1
            self.texture_id,
            0,
        );
1
        self.gl_context
1
            .draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
1
        let clear_color: ColorF = self.background_color.into();
1
        self.gl_context
1
            .clear_color(clear_color.r, clear_color.g, clear_color.b, clear_color.a);
1
        self.gl_context.clear_depth(0.0);
1
        self.gl_context
1
            .clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
        // also runs on an unwinding panic), so we restore state and let the
        // guard reclaim the GL objects at scope exit.
1
        saved.restore(&self.gl_context);
1
        drop(fbo_rbo_guard);
1
    }
7
    #[must_use] pub fn get_descriptor(&self) -> ImageDescriptor {
7
        ImageDescriptor {
7
            format: self.format,
7
            width: self.size.width as usize,
7
            height: self.size.height as usize,
7
            stride: None.into(),
7
            offset: 0,
7
            flags: ImageDescriptorFlags {
7
                is_opaque: self.flags.is_opaque,
7
                // The texture gets mapped 1:1 onto the display, so there is no need for mipmaps
7
                allow_mipmaps: false,
7
            },
7
        }
7
    }
    /// Draws a `TessellatedGPUSvgNode` with the given color to the texture
    pub fn draw_tesselated_svg_gpu_node(
        &mut self,
        node: &TessellatedGPUSvgNode,
        size: PhysicalSizeU32,
        color: ColorU,
        transforms: StyleTransformVec,
    ) -> bool {
        node.draw(self, size, color, transforms)
    }
    /// Draws a `TessellatedColoredGPUSvgNode` to the texture
    pub fn draw_tesselated_colored_svg_gpu_node(
        &mut self,
        node: &crate::svg::TessellatedColoredGPUSvgNode,
        size: PhysicalSizeU32,
        transforms: StyleTransformVec,
    ) -> bool {
        node.draw(self, size, transforms)
    }
}
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(C)]
pub struct TextureFlags {
    /// Whether this texture contains an alpha component
    pub is_opaque: bool,
    /// Optimization: use the compositor instead of OpenGL for energy optimization
    pub is_video_texture: bool,
}
impl ::core::fmt::Display for Texture {
3
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3
        write!(
3
            f,
3
            "Texture {{ id: {}, {}x{} }}",
            self.texture_id, self.size.width, self.size.height
        )
3
    }
}
macro_rules! impl_traits_for_gl_object {
    ($struct_name:ident, $gl_id_field:ident) => {
        impl ::core::fmt::Debug for $struct_name {
1
            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1
                write!(f, "{}", self)
1
            }
        }
        impl Hash for $struct_name {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.$gl_id_field.hash(state);
            }
        }
        impl PartialEq for $struct_name {
4
            fn eq(&self, other: &$struct_name) -> bool {
4
                self.$gl_id_field == other.$gl_id_field
4
            }
        }
        impl Eq for $struct_name {}
        impl PartialOrd for $struct_name {
            fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
                Some((self.$gl_id_field).cmp(&(other.$gl_id_field)))
            }
        }
        impl Ord for $struct_name {
            fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
                (self.$gl_id_field).cmp(&(other.$gl_id_field))
            }
        }
    };
}
impl Texture {
    /// GPU painting: stamp one soft-brush dab centered at (`cx`, `cy`) in texture
    /// pixel coordinates (origin top-left, matching [`RawImage::paint_dot`]).
    /// No-op if the GL context is unusable -- the caller should then use the CPU
    /// `RawImage` path (`GlContextPtr::is_gl_usable`).
7
    pub fn paint_dot(&mut self, cx: f32, cy: f32, brush: Brush) {
7
        self.paint_stroke(cx, cy, cx, cy, brush);
7
    }
    /// GPU painting: stamp dabs along (`x0`,`y0`)->(`x1`,`y1`) into this texture
    /// via an FBO + the soft-brush shader, alpha-over blended. Same spacing +
    /// falloff as the CPU `RawImage::paint_stroke`. No-op if GL is unusable.
    #[allow(clippy::suboptimal_flops)] // mul_add not guaranteed faster/available without target +fma; keep explicit a*b+c
    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
    #[allow(clippy::many_single_char_names)] // domain-standard colour/coordinate component names
13
    pub fn paint_stroke(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, brush: Brush) {
13
        let gl = self.gl_context.clone();
13
        let prog = gl.get_brush_shader();
13
        let (tw, th) = (self.size.width as f32, self.size.height as f32);
        // `!(radius > 0.0)` intentionally also rejects NaN (`radius <= 0.0` would not).
        #[allow(clippy::neg_cmp_op_on_partial_ord)]
13
        if prog == 0 || self.texture_id == 0 || !(brush.radius > 0.0) || tw <= 0.0 || th <= 0.0 {
13
            return;
        }
        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
        let vbo = gl.gen_buffers(1).get(0).copied().unwrap_or(0);
        if fbo == 0 || vbo == 0 {
            if fbo != 0 {
                gl.delete_framebuffers((&[fbo][..]).into());
            }
            if vbo != 0 {
                gl.delete_buffers((&[vbo][..]).into());
            }
            return;
        }
        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
        gl.framebuffer_texture_2d(
            gl::FRAMEBUFFER,
            gl::COLOR_ATTACHMENT0,
            gl::TEXTURE_2D,
            self.texture_id,
            0,
        );
        gl.viewport(0, 0, self.size.width as i32, self.size.height as i32);
        gl.enable(gl::BLEND);
        gl.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
        gl.use_program(prog);
        let a = (f32::from(brush.color.a) / 255.0) * brush.flow.clamp(0.0, 1.0);
        gl.uniform_4f(
            gl.get_uniform_location(prog, "uColor"),
            f32::from(brush.color.r) / 255.0,
            f32::from(brush.color.g) / 255.0,
            f32::from(brush.color.b) / 255.0,
            a,
        );
        gl.uniform_1f(gl.get_uniform_location(prog, "uHardness"), brush.hardness);
        gl.bind_buffer(gl::ARRAY_BUFFER, vbo);
        gl.enable_vertex_attrib_array(0);
        gl.enable_vertex_attrib_array(1);
        gl.vertex_attrib_pointer_f32(0, 2, false, 16, 0);
        gl.vertex_attrib_pointer_f32(1, 2, false, 16, 8);
        let dx = x1 - x0;
        let dy = y1 - y0;
        let len = dx.hypot(dy);
        let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
        let n = ((len / step).floor() as i32).max(0);
        let r = brush.radius;
        for i in 0..=n {
            let t = if n == 0 { 1.0 } else { i as f32 / n as f32 };
            let px = x0 + dx * t;
            let py = y0 + dy * t;
            // dab bbox -> NDC; y is flipped so (0,0) is top-left like the CPU path.
            let nx = |x: f32| (x / tw) * 2.0 - 1.0;
            let ny = |y: f32| 1.0 - (y / th) * 2.0;
            let (l, rr, tp, bt) = (nx(px - r), nx(px + r), ny(py - r), ny(py + r));
            // TRIANGLE_STRIP: TL, BL, TR, BR -- interleaved (pos.x, pos.y, uv.x, uv.y).
            let verts: [f32; 16] = [
                l, tp, -1.0, -1.0, l, bt, -1.0, 1.0, rr, tp, 1.0, -1.0, rr, bt, 1.0, 1.0,
            ];
            gl.buffer_data_untyped(
                gl::ARRAY_BUFFER,
                (verts.len() * size_of::<f32>()) as isize,
                GlVoidPtrConst {
                    ptr: verts.as_ptr() as *const GLvoid,
                    run_destructor: false,
                },
                gl::STREAM_DRAW,
            );
            gl.draw_arrays(gl::TRIANGLE_STRIP, 0, 4);
        }
        gl.disable_vertex_attrib_array(0);
        gl.disable_vertex_attrib_array(1);
        gl.bind_buffer(gl::ARRAY_BUFFER, 0);
        gl.disable(gl::BLEND);
        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
        gl.delete_buffers((&[vbo][..]).into());
        gl.delete_framebuffers((&[fbo][..]).into());
13
    }
    /// Read this texture's pixels back into an RGBA8 `RawImage` (top-left origin)
    /// -- for exporting the painted canvas to disk. Binds an FBO + glReadPixels.
    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
7
    #[must_use] pub fn copy_to_raw_image(&self) -> RawImage {
7
        let gl = self.gl_context.clone();
7
        let (w, h) = (self.size.width as i32, self.size.height as i32);
7
        if self.texture_id == 0 || w <= 0 || h <= 0 {
6
            return RawImage::null_image();
1
        }
1
        let fbo = gl.gen_framebuffers(1).get(0).copied().unwrap_or(0);
1
        if fbo == 0 {
1
            return RawImage::null_image();
        }
        gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
        gl.framebuffer_texture_2d(
            gl::FRAMEBUFFER,
            gl::COLOR_ATTACHMENT0,
            gl::TEXTURE_2D,
            self.texture_id,
            0,
        );
        let pixels = gl.read_pixels(0, 0, w, h, gl::RGBA, gl::UNSIGNED_BYTE);
        gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
        gl.delete_framebuffers((&[fbo][..]).into());
        // glReadPixels uses a bottom-left origin; flip rows to top-left for saving.
        let mut bytes = pixels.into_library_owned_vec();
        let row = (w as usize) * 4;
        let hh = h as usize;
        if row > 0 && bytes.len() >= row * hh {
            for y in 0..hh / 2 {
                let yi = y * row;
                let yo = (hh - 1 - y) * row;
                for k in 0..row {
                    bytes.swap(yi + k, yo + k);
                }
            }
        }
        RawImage {
            pixels: RawImageData::U8(bytes.into()),
            width: w as usize,
            height: h as usize,
            premultiplied_alpha: true,
            data_format: RawImageFormat::RGBA8,
            tag: Vec::new().into(),
        }
7
    }
}
impl_traits_for_gl_object!(Texture, texture_id);
impl Drop for Texture {
38
    fn drop(&mut self) {
        // AUDIT: mirror `GlContextPtr::drop`. Without this guard a C-ABI
        // double-drop (drop_in_place run twice on the same byte-copied struct)
        // does a second `fetch_sub` on the already-freed refcount box (UAF) and
        // a second `delete_textures`. The first drop clears `run_destructor`, so
        // the second is a no-op.
38
        if !self.run_destructor {
            return;
38
        }
38
        self.run_destructor = false;
38
        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
38
        if copies == 1 {
22
            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
22
            self.gl_context
22
                .delete_textures((&[self.texture_id])[..].into());
22
        }
38
    }
}
/// Describes the vertex layout and offsets
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct VertexLayout {
    pub fields: VertexAttributeVec,
}
impl_vec!(VertexAttribute, VertexAttributeVec, VertexAttributeVecDestructor, VertexAttributeVecDestructorType, VertexAttributeVecSlice, OptionVertexAttribute);
impl_vec_debug!(VertexAttribute, VertexAttributeVec);
impl_vec_partialord!(VertexAttribute, VertexAttributeVec);
impl_vec_ord!(VertexAttribute, VertexAttributeVec);
impl_vec_clone!(
    VertexAttribute,
    VertexAttributeVec,
    VertexAttributeVecDestructor
);
impl_vec_partialeq!(VertexAttribute, VertexAttributeVec);
impl_vec_eq!(VertexAttribute, VertexAttributeVec);
impl_vec_hash!(VertexAttribute, VertexAttributeVec);
impl VertexLayout {
    /// Submits the vertex buffer description to OpenGL
    // OpenGL binding: vertex-attribute layout (locations, item counts, strides,
    // offsets) passed to the gl API as GLuint/GLint/GLsizei; values are GL-bounded.
    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
    pub fn bind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
        const VERTICES_ARE_NORMALIZED: bool = false;
        let mut offset = 0;
        let stride_between_vertices: usize =
            self.fields.iter().map(VertexAttribute::get_stride).sum();
        for vertex_attribute in &self.fields {
            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
                |ll| *ll as i32,
            );
            gl_context.vertex_attrib_pointer(
                attribute_location as u32,
                vertex_attribute.item_count as i32,
                vertex_attribute.attribute_type.get_gl_id(),
                VERTICES_ARE_NORMALIZED,
                stride_between_vertices as i32,
                offset as u32,
            );
            gl_context.enable_vertex_attrib_array(attribute_location as u32);
            offset += vertex_attribute.get_stride();
        }
    }
    /// Unsets the vertex buffer description
    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
    #[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts
    pub fn unbind(&self, gl_context: &Rc<GenericGlContext>, program_id: GLuint) {
        for vertex_attribute in &self.fields {
            let attribute_location = vertex_attribute.layout_location.as_option().map_or_else(
                || gl_context.get_attrib_location(program_id, vertex_attribute.va_name.as_str()),
                |ll| *ll as i32,
            );
            gl_context.disable_vertex_attrib_array(attribute_location as u32);
        }
    }
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct VertexAttribute {
    /// Attribute name of the vertex attribute in the vertex shader, i.e. `"vAttrXY"`
    pub va_name: AzString,
    /// If the vertex shader has a specific location, (like `layout(location = 2) vAttrXY`),
    /// use this instead of the name to look up the uniform location.
    pub layout_location: OptionUsize,
    /// Type of items of this attribute (i.e. for a `FloatVec2`, would be
    /// `VertexAttributeType::Float`)
    pub attribute_type: VertexAttributeType,
    /// Number of items of this attribute (i.e. for a `FloatVec2`, would be `2` (= 2 consecutive
    /// f32 values))
    pub item_count: usize,
}
impl_option!(
    VertexAttribute,
    OptionVertexAttribute,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl VertexAttribute {
15
    #[must_use] pub const fn get_stride(&self) -> usize {
15
        self.attribute_type.get_mem_size() * self.item_count
15
    }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum VertexAttributeType {
    /// Vertex attribute has type `f32`
    Float,
    /// Vertex attribute has type `f64`
    Double,
    /// Vertex attribute has type `u8`
    UnsignedByte,
    /// Vertex attribute has type `u16`
    UnsignedShort,
    /// Vertex attribute has type `u32`
    UnsignedInt,
}
impl VertexAttributeType {
    /// Returns the OpenGL id for the vertex attribute type, ex. `gl::UNSIGNED_BYTE` for
    /// `VertexAttributeType::UnsignedByte`.
27
    #[must_use] pub const fn get_gl_id(&self) -> GLuint {
        use self::VertexAttributeType::{Float, Double, UnsignedByte, UnsignedShort, UnsignedInt};
27
        match self {
6
            Float => gl::FLOAT,
5
            Double => gl::DOUBLE,
6
            UnsignedByte => gl::UNSIGNED_BYTE,
5
            UnsignedShort => gl::UNSIGNED_SHORT,
5
            UnsignedInt => gl::UNSIGNED_INT,
        }
27
    }
35
    #[must_use] pub const fn get_mem_size(&self) -> usize {
        use core::mem;
        use self::VertexAttributeType::{Float, Double, UnsignedByte, UnsignedShort, UnsignedInt};
35
        match self {
8
            Float => size_of::<f32>(),
7
            Double => size_of::<f64>(),
8
            UnsignedByte => size_of::<u8>(),
6
            UnsignedShort => size_of::<u16>(),
6
            UnsignedInt => size_of::<u32>(),
        }
35
    }
}
pub trait VertexLayoutDescription {
    fn get_description() -> VertexLayout;
}
#[derive(Debug, PartialEq, Eq, PartialOrd)]
#[repr(C)]
pub struct VertexArrayObject {
    pub vertex_layout: VertexLayout,
    pub vao_id: GLuint,
    pub gl_context: GlContextPtr,
    pub refcount: *const AtomicUsize,
    pub run_destructor: bool,
}
impl VertexArrayObject {
3
    #[must_use] pub fn new(vertex_layout: VertexLayout, vao_id: GLuint, gl_context: GlContextPtr) -> Self {
3
        Self {
3
            vertex_layout,
3
            vao_id,
3
            gl_context,
3
            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
3
            run_destructor: true,
3
        }
3
    }
}
impl Clone for VertexArrayObject {
16
    fn clone(&self) -> Self {
16
        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
16
        Self {
16
            vertex_layout: self.vertex_layout.clone(),
16
            vao_id: self.vao_id,
16
            gl_context: self.gl_context.clone(),
16
            refcount: self.refcount,
16
            run_destructor: true,
16
        }
16
    }
}
impl Drop for VertexArrayObject {
19
    fn drop(&mut self) {
        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
        // freeing the refcount box twice (use-after-free) + double delete.
19
        if !self.run_destructor {
            return;
19
        }
19
        self.run_destructor = false;
19
        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
19
        if copies == 1 {
3
            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
3
            self.gl_context
3
                .delete_vertex_arrays((&[self.vao_id])[..].into());
16
        }
19
    }
}
#[repr(C)]
pub struct VertexBuffer {
    pub vao: VertexArrayObject,
    pub vertex_buffer_id: GLuint,
    pub vertex_buffer_len: usize,
    pub index_buffer_id: GLuint,
    pub index_buffer_len: usize,
    pub refcount: *const AtomicUsize,
    pub index_buffer_format: IndexBufferFormat,
    pub run_destructor: bool,
}
impl fmt::Display for VertexBuffer {
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1
        write!(
1
            f,
1
            "VertexBuffer {{ buffer: {} (length: {}) }}",
            self.vertex_buffer_id, self.vertex_buffer_len
        )
1
    }
}
impl_traits_for_gl_object!(VertexBuffer, vertex_buffer_id);
impl Clone for VertexBuffer {
8
    fn clone(&self) -> Self {
8
        unsafe { (*self.refcount).fetch_add(1, AtomicOrdering::SeqCst) };
8
        Self {
8
            vao: self.vao.clone(),
8
            vertex_buffer_id: self.vertex_buffer_id,
8
            vertex_buffer_len: self.vertex_buffer_len,
8
            index_buffer_id: self.index_buffer_id,
8
            index_buffer_len: self.index_buffer_len,
8
            refcount: self.refcount,
8
            index_buffer_format: self.index_buffer_format,
8
            run_destructor: true,
8
        }
8
    }
}
impl Drop for VertexBuffer {
10
    fn drop(&mut self) {
        // AUDIT: mirror `GlContextPtr::drop` — guard against a C-ABI double-drop
        // freeing the refcount box twice (use-after-free) + double delete.
10
        if !self.run_destructor {
            return;
10
        }
10
        self.run_destructor = false;
10
        let copies = unsafe { (*self.refcount).fetch_sub(1, AtomicOrdering::SeqCst) };
10
        if copies == 1 {
2
            self.vao.vertex_layout = VertexLayout {
2
                fields: VertexAttributeVec::from_const_slice(&[]),
2
            };
2
            drop(unsafe { Box::from_raw(self.refcount.cast_mut()) });
2
            self.vao
2
                .gl_context
2
                .delete_buffers((&[self.vertex_buffer_id, self.index_buffer_id])[..].into());
8
        }
10
    }
}
impl VertexBuffer {
    /// # Panics
    ///
    /// Panics if the GL driver failed to create the vertex-array/buffer objects
    /// (the returned id lists are empty).
    // OpenGL binding: buffer sizes / vertex counts passed to the gl API as
    // GLsizeiptr/GLint; values are GL-bounded.
    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
1
    pub fn new<T: VertexLayoutDescription>(
1
        gl_context: GlContextPtr,
1
        shader_program_id: GLuint,
1
        vertices: &[T],
1
        indices: &[u32],
1
        index_buffer_format: IndexBufferFormat,
1
    ) -> Self {
        use core::mem;
        // Save the OpenGL state
1
        let mut current_vertex_array = [0_i32];
1
        gl_context.get_integer_v(gl::VERTEX_ARRAY, (&mut current_vertex_array[..]).into());
1
        let vertex_array_object = gl_context.gen_vertex_arrays(1);
1
        let vertex_array_object = vertex_array_object.get(0).unwrap();
1
        let vertex_buffer_id = gl_context.gen_buffers(1);
1
        let vertex_buffer_id = vertex_buffer_id.get(0).unwrap();
1
        let index_buffer_id = gl_context.gen_buffers(1);
1
        let index_buffer_id = index_buffer_id.get(0).unwrap();
1
        gl_context.bind_vertex_array(*vertex_array_object);
        // Upload vertex data to GPU
1
        gl_context.bind_buffer(gl::ARRAY_BUFFER, *vertex_buffer_id);
1
        gl_context.buffer_data_untyped(
            gl::ARRAY_BUFFER,
1
            size_of_val(vertices) as isize,
1
            GlVoidPtrConst {
1
                ptr: vertices.as_ptr() as *const core::ffi::c_void,
1
                run_destructor: true,
1
            },
            gl::STATIC_DRAW,
        );
        // Generate the index buffer + upload data
1
        gl_context.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, *index_buffer_id);
1
        gl_context.buffer_data_untyped(
            gl::ELEMENT_ARRAY_BUFFER,
1
            size_of_val(indices) as isize,
1
            GlVoidPtrConst {
1
                ptr: indices.as_ptr() as *const core::ffi::c_void,
1
                run_destructor: true,
1
            },
            gl::STATIC_DRAW,
        );
1
        let vertex_description = T::get_description();
1
        vertex_description.bind(&gl_context.ptr.ptr, shader_program_id);
        // Reset the OpenGL state
1
        gl_context.bind_vertex_array(current_vertex_array[0] as u32);
1
        Self::new_raw(
1
            *vertex_buffer_id,
1
            vertices.len(),
1
            VertexArrayObject::new(vertex_description, *vertex_array_object, gl_context),
1
            *index_buffer_id,
1
            indices.len(),
1
            index_buffer_format,
        )
1
    }
2
    #[must_use] pub fn new_raw(
2
        vertex_buffer_id: GLuint,
2
        vertex_buffer_len: usize,
2
        vao: VertexArrayObject,
2
        index_buffer_id: GLuint,
2
        index_buffer_len: usize,
2
        index_buffer_format: IndexBufferFormat,
2
    ) -> Self {
2
        Self {
2
            vertex_buffer_id,
2
            vertex_buffer_len,
2
            vao,
2
            index_buffer_id,
2
            index_buffer_len,
2
            index_buffer_format,
2
            refcount: Box::into_raw(Box::new(AtomicUsize::new(1))),
2
            run_destructor: true,
2
        }
2
    }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum GlApiVersion {
    Gl { major: usize, minor: usize },
    GlEs { major: usize, minor: usize },
}
impl GlApiVersion {
    /// Returns the OpenGL version of the context
    #[allow(clippy::cast_sign_loss)] // OpenGL/graphics binding: GL-bounded numeric casts
    #[must_use] pub fn get(gl_context: &GlContextPtr) -> Self {
        let mut major = [0];
        gl_context.get_integer_v(gl::MAJOR_VERSION, (&mut major[..]).into());
        let mut minor = [0];
        gl_context.get_integer_v(gl::MINOR_VERSION, (&mut minor[..]).into());
        let major = major[0] as usize;
        let minor = minor[0] as usize;
        match gl_context.get_type() {
            GlType::Gl => Self::Gl { major, minor },
            GlType::Gles => Self::GlEs { major, minor },
        }
    }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum IndexBufferFormat {
    Points,
    Lines,
    LineStrip,
    Triangles,
    TriangleStrip,
    TriangleFan,
}
impl IndexBufferFormat {
    /// Returns the `gl::TRIANGLE_STRIP` / `gl::POINTS`, etc.
32
    #[must_use] pub const fn get_gl_id(&self) -> GLuint {
        use self::IndexBufferFormat::{Points, Lines, LineStrip, Triangles, TriangleStrip, TriangleFan};
32
        match self {
6
            Points => gl::POINTS,
5
            Lines => gl::LINES,
5
            LineStrip => gl::LINE_STRIP,
5
            Triangles => gl::TRIANGLES,
6
            TriangleStrip => gl::TRIANGLE_STRIP,
5
            TriangleFan => gl::TRIANGLE_FAN,
        }
32
    }
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct Uniform {
    pub uniform_name: AzString,
    pub uniform_type: UniformType,
}
impl Uniform {
5
    pub fn create<S: Into<AzString>>(name: S, uniform_type: UniformType) -> Self {
5
        Self {
5
            uniform_name: name.into(),
5
            uniform_type,
5
        }
5
    }
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[repr(C, u8)]
pub enum UniformType {
    Float(f32),
    FloatVec2([f32; 2]),
    FloatVec3([f32; 3]),
    FloatVec4([f32; 4]),
    Int(i32),
    IntVec2([i32; 2]),
    IntVec3([i32; 3]),
    IntVec4([i32; 4]),
    UnsignedInt(u32),
    UnsignedIntVec2([u32; 2]),
    UnsignedIntVec3([u32; 3]),
    UnsignedIntVec4([u32; 4]),
    Matrix2 {
        transpose: bool,
        matrix: [f32; 2 * 2],
    },
    Matrix3 {
        transpose: bool,
        matrix: [f32; 3 * 3],
    },
    Matrix4 {
        transpose: bool,
        matrix: [f32; 4 * 4],
    },
}
impl UniformType {
    /// Set a specific uniform
    pub fn set(self, gl_context: &Rc<GenericGlContext>, location: GLint) {
        use self::UniformType::{Float, FloatVec2, FloatVec3, FloatVec4, Int, IntVec2, IntVec3, IntVec4, UnsignedInt, UnsignedIntVec2, UnsignedIntVec3, UnsignedIntVec4, Matrix2, Matrix3, Matrix4};
        match self {
            Float(r) => gl_context.uniform_1f(location, r),
            FloatVec2([r, g]) => gl_context.uniform_2f(location, r, g),
            FloatVec3([r, g, b]) => gl_context.uniform_3f(location, r, g, b),
            FloatVec4([r, g, b, a]) => gl_context.uniform_4f(location, r, g, b, a),
            Int(r) => gl_context.uniform_1i(location, r),
            IntVec2([r, g]) => gl_context.uniform_2i(location, r, g),
            IntVec3([r, g, b]) => gl_context.uniform_3i(location, r, g, b),
            IntVec4([r, g, b, a]) => gl_context.uniform_4i(location, r, g, b, a),
            UnsignedInt(r) => gl_context.uniform_1ui(location, r),
            UnsignedIntVec2([r, g]) => gl_context.uniform_2ui(location, r, g),
            UnsignedIntVec3([r, g, b]) => gl_context.uniform_3ui(location, r, g, b),
            UnsignedIntVec4([r, g, b, a]) => gl_context.uniform_4ui(location, r, g, b, a),
            Matrix2 { transpose, matrix } => {
                gl_context.uniform_matrix_2fv(location, transpose, &matrix[..]);
            }
            Matrix3 { transpose, matrix } => {
                gl_context.uniform_matrix_3fv(location, transpose, &matrix[..]);
            }
            Matrix4 { transpose, matrix } => {
                gl_context.uniform_matrix_4fv(location, transpose, &matrix[..]);
            }
        }
    }
}
#[repr(C)]
pub struct GlShader {
    pub program_id: GLuint,
    pub gl_context: GlContextPtr,
    /// AUDIT: guards against a double-drop deleting the same GL program twice
    /// (`drop_in_place` run twice on a byte-copied struct). Set `true` on
    /// construction; the first drop clears it so a second drop is a no-op.
    pub run_destructor: bool,
}
impl ::core::fmt::Display for GlShader {
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
        write!(f, "GlShader {{ program_id: {} }}", self.program_id)
    }
}
impl_traits_for_gl_object!(GlShader, program_id);
impl Drop for GlShader {
    fn drop(&mut self) {
        // AUDIT: mirror `GlContextPtr::drop` — a C-ABI double-drop would call
        // `delete_program` on the same id twice. The first drop clears the flag.
        if !self.run_destructor {
            return;
        }
        self.run_destructor = false;
        self.gl_context.delete_program(self.program_id);
    }
}
#[repr(C)]
#[derive(Clone)]
pub struct VertexShaderCompileError {
    pub error_id: i32,
    pub info_log: AzString,
}
impl_traits_for_gl_object!(VertexShaderCompileError, error_id);
impl ::core::fmt::Display for VertexShaderCompileError {
5
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
5
        write!(f, "E{}: {}", self.error_id, self.info_log)
5
    }
}
#[repr(C)]
#[derive(Clone)]
pub struct FragmentShaderCompileError {
    pub error_id: i32,
    pub info_log: AzString,
}
impl_traits_for_gl_object!(FragmentShaderCompileError, error_id);
impl ::core::fmt::Display for FragmentShaderCompileError {
2
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
2
        write!(f, "E{}: {}", self.error_id, self.info_log)
2
    }
}
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum GlShaderCompileError {
    Vertex(VertexShaderCompileError),
    Fragment(FragmentShaderCompileError),
}
impl ::core::fmt::Display for GlShaderCompileError {
4
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
        use self::GlShaderCompileError::{Vertex, Fragment};
4
        match self {
3
            Vertex(vert_err) => write!(f, "Failed to compile vertex shader: {vert_err}"),
1
            Fragment(frag_err) => write!(f, "Failed to compile fragment shader: {frag_err}"),
        }
4
    }
}
impl ::core::fmt::Debug for GlShaderCompileError {
1
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1
        write!(f, "{self}")
1
    }
}
#[repr(C)]
#[derive(Clone)]
pub struct GlShaderLinkError {
    pub error_id: i32,
    pub info_log: AzString,
}
impl_traits_for_gl_object!(GlShaderLinkError, error_id);
impl ::core::fmt::Display for GlShaderLinkError {
3
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
3
        write!(f, "E{}: {}", self.error_id, self.info_log)
3
    }
}
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum GlShaderCreateError {
    Compile(GlShaderCompileError),
    Link(GlShaderLinkError),
    NoShaderCompiler,
}
impl ::core::fmt::Display for GlShaderCreateError {
4
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
        use self::GlShaderCreateError::{Compile, Link, NoShaderCompiler};
4
        match self {
            Compile(compile_err) => write!(f, "Shader compile error: {compile_err}"),
3
            Link(link_err) => write!(f, "Shader linking error: {link_err}"),
            NoShaderCompiler => {
1
                write!(f, "OpenGL implementation doesn't include a shader compiler")
            }
        }
4
    }
}
impl ::core::fmt::Debug for GlShaderCreateError {
1
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1
        write!(f, "{self}")
1
    }
}
impl GlShader {
    /// Compiles and creates a new OpenGL shader, created from a vertex and a fragment shader
    /// string.
    ///
    /// If the shader fails to compile, the shader object gets automatically deleted, no cleanup
    /// necessary.
    #[allow(clippy::cast_possible_truncation)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
    /// # Errors
    ///
    /// Returns an error if the OpenGL implementation has no shader compiler, or if the vertex/fragment shader fails to compile or link.
5
    pub fn new(
5
        gl_context: &GlContextPtr,
5
        vertex_shader: &str,
5
        fragment_shader: &str,
5
    ) -> Result<Self, GlShaderCreateError> {
        // Check whether the OpenGL implementation supports a shader compiler...
5
        let mut shader_compiler_supported = [gl::FALSE as u8];
5
        gl_context.get_boolean_v(
            gl::SHADER_COMPILER,
5
            (&mut shader_compiler_supported[..]).into(),
        );
5
        if u32::from(shader_compiler_supported[0]) == gl::FALSE {
            // Implementation only supports binary shaders
5
            return Err(GlShaderCreateError::NoShaderCompiler);
        }
        // Compile vertex shader
        let vertex_shader_object = gl_context.create_shader(gl::VERTEX_SHADER);
        gl_context.shader_source(
            vertex_shader_object,
            vec![AzString::from(vertex_shader.to_string())].into(),
        );
        gl_context.compile_shader(vertex_shader_object);
        if let Some(error_id) = get_gl_shader_error(gl_context, vertex_shader_object) {
            let info_log = gl_context.get_shader_info_log(vertex_shader_object);
            gl_context.delete_shader(vertex_shader_object);
            return Err(GlShaderCreateError::Compile(GlShaderCompileError::Vertex(
                VertexShaderCompileError {
                    error_id,
                    info_log,
                },
            )));
        }
        // Compile fragment shader
        let fragment_shader_object = gl_context.create_shader(gl::FRAGMENT_SHADER);
        gl_context.shader_source(
            fragment_shader_object,
            vec![AzString::from(fragment_shader.to_string())].into(),
        );
        gl_context.compile_shader(fragment_shader_object);
        if let Some(error_id) = get_gl_shader_error(gl_context, fragment_shader_object) {
            let info_log = gl_context.get_shader_info_log(fragment_shader_object);
            gl_context.delete_shader(vertex_shader_object);
            gl_context.delete_shader(fragment_shader_object);
            return Err(GlShaderCreateError::Compile(
                GlShaderCompileError::Fragment(FragmentShaderCompileError {
                    error_id,
                    info_log,
                }),
            ));
        }
        // Link program
        let program_id = gl_context.create_program();
        gl_context.attach_shader(program_id, vertex_shader_object);
        gl_context.attach_shader(program_id, fragment_shader_object);
        gl_context.link_program(program_id);
        if let Some(error_id) = get_gl_program_error(gl_context, program_id) {
            let info_log = gl_context.get_program_info_log(program_id);
            gl_context.delete_shader(vertex_shader_object);
            gl_context.delete_shader(fragment_shader_object);
            gl_context.delete_program(program_id);
            return Err(GlShaderCreateError::Link(GlShaderLinkError {
                error_id,
                info_log,
            }));
        }
        gl_context.delete_shader(vertex_shader_object);
        gl_context.delete_shader(fragment_shader_object);
        Ok(Self {
            program_id,
            gl_context: gl_context.clone(),
            run_destructor: true,
        })
5
    }
    /// Draws vertex buffers, index buffers + uniforms to the texture
    ///
    /// # Panics
    ///
    /// Panics if no framebuffer/depthbuffer was allocated (the GL object lists are empty).
    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose parser/builder/dispatch (one branch per input variant)
    pub fn draw(
        // shader to use for drawing
        shader_program_id: GLuint,
        // note: texture is &mut so the texture is reusable -
        texture: &mut Texture,
        // buffers + uniforms to draw
        buffers: &[(&VertexBuffer, &[Uniform])],
    ) {
        use alloc::collections::btree_map::BTreeMap;
        const INDEX_TYPE: GLuint = gl::UNSIGNED_INT;
        let texture_size = texture.size;
        let gl_context = &texture.gl_context;
        let saved = GlStateSave::save(gl_context);
        // save draw()-specific state not covered by GlStateSave
        let mut current_blend_enabled = [0_u8];
        let mut current_primitive_restart_enabled = [0_u8];
        gl_context.get_boolean_v(gl::BLEND, (&mut current_blend_enabled[..]).into());
        gl_context.get_boolean_v(
            gl::PRIMITIVE_RESTART,
            (&mut current_primitive_restart_enabled[..]).into(),
        );
        // 1. Create the framebuffer
        let framebuffers = gl_context.gen_framebuffers(1);
        let framebuffer_id = *framebuffers.get(0).unwrap();
        // AUDIT: register the FBO for cleanup BEFORE the next fallible step so a
        // panic anywhere below (incl. `gen_renderbuffers().get(0).unwrap()`)
        // can't leak the FBO/RBO. Guard's Drop runs on unwind too.
        let mut fbo_rbo_guard = FboRboGuard {
            gl_context,
            framebuffer_id,
            renderbuffer_id: 0,
        };
        gl_context.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_id);
        let depthbuffers = gl_context.gen_renderbuffers(1);
        let depthbuffer_id = *depthbuffers.get(0).unwrap();
        fbo_rbo_guard.renderbuffer_id = depthbuffer_id;
        gl_context.bind_texture(gl::TEXTURE_2D, texture.texture_id);
        gl_context.tex_image_2d(
            gl::TEXTURE_2D,
            0,
            gl::RGBA as i32, // NOT RGBA8 - will generate INVALID_ENUM!
            texture_size.width as i32,
            texture_size.height as i32,
            0,
            gl::RGBA, // gl::BGRA?
            gl::UNSIGNED_BYTE,
            None.into(),
        );
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
        gl_context.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
        gl_context.bind_renderbuffer(gl::RENDERBUFFER, depthbuffer_id);
        gl_context.renderbuffer_storage(
            gl::RENDERBUFFER,
            gl::DEPTH_COMPONENT,
            texture_size.width as i32,
            texture_size.height as i32,
        );
        gl_context.framebuffer_renderbuffer(
            gl::FRAMEBUFFER,
            gl::DEPTH_ATTACHMENT,
            gl::RENDERBUFFER,
            depthbuffer_id,
        );
        gl_context.framebuffer_texture_2d(
            gl::FRAMEBUFFER,
            gl::COLOR_ATTACHMENT0,
            gl::TEXTURE_2D,
            texture.texture_id,
            0,
        );
        gl_context.draw_buffers([gl::COLOR_ATTACHMENT0][..].into());
        #[cfg(feature = "std")]
        {
            let fb_check = gl_context.check_frame_buffer_status(gl::FRAMEBUFFER);
            match fb_check {
                gl::FRAMEBUFFER_COMPLETE => {}
                gl::FRAMEBUFFER_UNDEFINED => {
                    println!("GL_FRAMEBUFFER_UNDEFINED");
                }
                gl::FRAMEBUFFER_INCOMPLETE_ATTACHMENT => {
                    println!("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
                }
                gl::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT => {
                    println!("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
                }
                gl::FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER => {
                    println!("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
                }
                gl::FRAMEBUFFER_INCOMPLETE_READ_BUFFER => {
                    println!("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
                }
                gl::FRAMEBUFFER_UNSUPPORTED => {
                    println!("GL_FRAMEBUFFER_UNSUPPORTED");
                }
                gl::FRAMEBUFFER_INCOMPLETE_MULTISAMPLE => {
                    println!("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
                }
                gl::FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS => {
                    println!("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS");
                }
                o => {
                    println!("glFramebufferStatus returned unknown return code: {o}");
                }
            }
        }
        gl_context.viewport(0, 0, texture_size.width as i32, texture_size.height as i32);
        gl_context.enable(gl::BLEND);
        // Use GL_PRIMITIVE_RESTART (OpenGL 3.1+) instead of
        // GL_PRIMITIVE_RESTART_FIXED_INDEX (4.3+) for macOS compatibility.
        gl_context.enable(gl::PRIMITIVE_RESTART);
        unsafe {
            let gl = gl_context.get();
            if !gl.glPrimitiveRestartIndex.is_null() {
                let func: extern "system" fn(u32) =
                    core::mem::transmute(gl.glPrimitiveRestartIndex);
                func(GL_RESTART_INDEX); // u32::MAX
            }
        }
        gl_context.disable(gl::MULTISAMPLE);
        gl_context.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); // TODO: enable / disable
        gl_context.use_program(shader_program_id);
        // Avoid multiple calls to get_uniform_location by caching the uniform locations
        let mut uniform_locations: BTreeMap<AzString, i32> = BTreeMap::new();
        let mut max_uniform_len = 0;
        for (_, uniforms) in buffers {
            for uniform in *uniforms {
                if !uniform_locations.contains_key(&uniform.uniform_name) {
                    uniform_locations.insert(
                        uniform.uniform_name.clone(),
                        gl_context.get_uniform_location(
                            shader_program_id,
                            uniform.uniform_name.as_str(),
                        ),
                    );
                }
            }
            max_uniform_len = max_uniform_len.max(uniforms.len());
        }
        let mut current_uniforms = vec![None; max_uniform_len];
        // Since the description of the vertex buffers is always the same,
        // only the first layer needs to bind its VAO
        // Draw the actual layers
        for (vertex_index_buffer, uniforms) in buffers {
            gl_context.bind_vertex_array(vertex_index_buffer.vao.vao_id);
            gl_context.bind_buffer(gl::ARRAY_BUFFER, vertex_index_buffer.vertex_buffer_id);
            gl_context.bind_buffer(
                gl::ELEMENT_ARRAY_BUFFER,
                vertex_index_buffer.index_buffer_id,
            );
            // Only set the uniform if the value has changed
            for (uniform_index, uniform) in uniforms.iter().enumerate() {
                if current_uniforms[uniform_index] != Some(uniform.uniform_type) {
                    let uniform_location = uniform_locations[&uniform.uniform_name];
                    uniform.uniform_type.set(gl_context.get(), uniform_location);
                    current_uniforms[uniform_index] = Some(uniform.uniform_type);
                }
            }
            gl_context.draw_elements(
                vertex_index_buffer.index_buffer_format.get_gl_id(),
                vertex_index_buffer.index_buffer_len as i32,
                INDEX_TYPE,
                0,
            );
        }
        // Reset draw()-specific state
        if u32::from(current_blend_enabled[0]) == gl::FALSE {
            gl_context.disable(gl::BLEND);
        }
        if u32::from(current_primitive_restart_enabled[0]) == gl::FALSE {
            gl_context.disable(gl::PRIMITIVE_RESTART);
        }
        // AUDIT: FBO/RBO deletion is handled by `fbo_rbo_guard`'s Drop (which
        // also runs on an unwinding panic) — reclaim them explicitly here so
        // the deletion order matches the original (delete before texture
        // metadata writes / after state restore).
        // Reset common GL state
        saved.restore(gl_context);
        drop(fbo_rbo_guard);
        texture.format = RawImageFormat::RGBA8;
        texture.flags = TextureFlags {
            is_opaque: false,
            is_video_texture: false,
        };
    }
}
#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
fn get_gl_shader_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
    let mut err = [0];
    context.get_shader_iv(shader_object, gl::COMPILE_STATUS, (&mut err[..]).into());
    let err_code = err[0];
    if err_code == gl::TRUE as i32 {
        None
    } else {
        Some(err_code)
    }
}
#[allow(clippy::cast_possible_wrap)] // OpenGL/graphics binding: GL-bounded numeric casts to GL* types
fn get_gl_program_error(context: &GlContextPtr, shader_object: GLuint) -> Option<i32> {
    let mut err = [0];
    context.get_program_iv(shader_object, gl::LINK_STATUS, (&mut err[..]).into());
    let err_code = err[0];
    if err_code == gl::TRUE as i32 {
        None
    } else {
        Some(err_code)
    }
}
#[cfg(test)]
mod audit_tests {
    use super::*;
    // AUDIT: FFI slice/str accessors must return an empty slice (not form a
    // slice over a null/dangling ptr, which is UB) when handed a null or
    // zero-length descriptor from C.
    #[test]
1
    fn refstr_null_and_empty_is_empty_str() {
1
        let null = Refstr {
1
            ptr: core::ptr::null(),
1
            len: 0,
1
        };
1
        assert_eq!(null.as_str(), "");
        // null ptr but nonzero len (garbage from FFI) must also not deref.
1
        let null_lenful = Refstr {
1
            ptr: core::ptr::null(),
1
            len: 5,
1
        };
1
        assert_eq!(null_lenful.as_str(), "");
1
        let s = "hello";
1
        let good: Refstr = s.into();
1
        assert_eq!(good.as_str(), "hello");
1
    }
    #[test]
1
    fn refstr_vec_ref_null_is_empty_slice() {
1
        let null = RefstrVecRef {
1
            ptr: core::ptr::null(),
1
            len: 3,
1
        };
1
        assert!(null.as_slice().is_empty());
1
    }
    #[test]
1
    fn u8_vec_ref_null_is_empty_slice() {
1
        let null = U8VecRef {
1
            ptr: core::ptr::null(),
1
            len: 8,
1
        };
1
        assert!(null.as_slice().is_empty());
1
        let data = [1u8, 2, 3];
1
        let good: U8VecRef = (&data[..]).into();
1
        assert_eq!(good.as_slice(), &[1, 2, 3]);
1
    }
}
#[cfg(test)]
#[allow(clippy::float_cmp)] // exact f32 compares are the point: these assert bit-exact round-trips / lossy-cast values
mod autotest_generated {
    use super::*;
    // ---------------------------------------------------------------------
    // Test scaffolding: a "null" GL context.
    //
    // Every field of `GenericGlContext` is a `*mut c_void` function pointer
    // (780 of them, no other field types), and every gl-context-loader method
    // null-checks its pointer and returns a default (0 / empty Vec / "") rather
    // than calling through. So an all-zero context is a SAFE no-op GL driver:
    // it lets us drive azul's whole wrapper surface off-GPU and assert how the
    // wrappers behave when the driver hands back degenerate values -- which is
    // exactly the "broken driver" path `is_gl_usable()` exists for.
    // ---------------------------------------------------------------------
    fn null_gl() -> Rc<GenericGlContext> {
        // SAFETY: `GenericGlContext` is `repr(C)` and every field is a raw
        // pointer, for which the all-zero bit pattern (null) is valid.
        Rc::new(unsafe { core::mem::zeroed::<GenericGlContext>() })
    }
    fn null_ctx() -> GlContextPtr {
        GlContextPtr::new(RendererType::Software, null_gl())
    }
    fn test_texture(texture_id: GLuint, width: u32, height: u32) -> Texture {
        Texture::create(
            texture_id,
            TextureFlags {
                is_opaque: false,
                is_video_texture: false,
            },
            PhysicalSizeU32 { width, height },
            ColorU {
                r: 1,
                g: 2,
                b: 3,
                a: 4,
            },
            null_ctx(),
            RawImageFormat::RGBA8,
        )
    }
    const ALL_ATTRIB_TYPES: [VertexAttributeType; 5] = [
        VertexAttributeType::Float,
        VertexAttributeType::Double,
        VertexAttributeType::UnsignedByte,
        VertexAttributeType::UnsignedShort,
        VertexAttributeType::UnsignedInt,
    ];
    const ALL_INDEX_FORMATS: [IndexBufferFormat; 6] = [
        IndexBufferFormat::Points,
        IndexBufferFormat::Lines,
        IndexBufferFormat::LineStrip,
        IndexBufferFormat::Triangles,
        IndexBufferFormat::TriangleStrip,
        IndexBufferFormat::TriangleFan,
    ];
    // =====================================================================
    // Refstr / *VecRef / *VecRefMut -- FFI slice+str accessors
    // (parsers: malformed / huge / boundary / unicode)
    // =====================================================================
    #[test]
    fn refstr_roundtrips_unicode_multibyte_and_combining_marks() {
        // Non-ASCII, emoji, and combining marks must survive byte-exactly:
        // `as_str` rebuilds the str via `from_utf8_unchecked` over the raw bytes.
        for s in [
            "\u{1F600}",
            "日本語のテキスト",
            "e\u{0301}\u{0328}combining",
            "\u{0}nul\u{0}embedded\u{0}",
            "\u{FFFD}\u{200B}zero-width",
        ] {
            let r: Refstr = s.into();
            assert_eq!(r.as_str(), s);
            assert_eq!(r.as_str().len(), s.len());
        }
    }
    #[test]
    fn refstr_len_zero_over_nonempty_buffer_is_empty_not_dangling() {
        // len == 0 must short-circuit even when ptr is a perfectly valid buffer.
        let backing = "not empty";
        let r = Refstr {
            ptr: backing.as_ptr(),
            len: 0,
        };
        assert_eq!(r.as_str(), "");
    }
    #[test]
    fn refstr_huge_input_does_not_hang() {
        // 1 MB single-token string: as_str is O(1) (no scan), so this must be instant.
        let huge = "x".repeat(1_000_000);
        let r: Refstr = huge.as_str().into();
        assert_eq!(r.as_str().len(), 1_000_000);
        assert_eq!(r.as_str(), huge.as_str());
    }
    #[test]
    fn refstr_debug_on_null_does_not_panic() {
        let null = Refstr {
            ptr: core::ptr::null(),
            len: usize::MAX,
        };
        // Debug goes through as_str(); a null guard failure here would be UB, not a panic.
        assert_eq!(alloc::format!("{null:?}"), "\"\"");
    }
    #[test]
    fn refstr_clone_preserves_ptr_and_len() {
        let s = "clone me";
        let r: Refstr = s.into();
        let c = r.clone();
        assert_eq!(c.as_str(), s);
        assert!(core::ptr::eq(c.ptr, r.ptr));
        assert_eq!(c.len, r.len);
    }
    #[test]
    fn every_vec_ref_with_null_ptr_and_nonzero_len_is_empty() {
        // The FFI boundary can hand us a null ptr with a garbage (nonzero) len.
        // Forming a slice over null is UB, so every accessor must return empty.
        const GARBAGE_LEN: usize = usize::MAX;
        assert!(RefstrVecRef {
            ptr: core::ptr::null(),
            len: GARBAGE_LEN
        }
        .as_slice()
        .is_empty());
        assert!(GLuintVecRef {
            ptr: core::ptr::null(),
            len: GARBAGE_LEN
        }
        .as_slice()
        .is_empty());
        assert!(GLenumVecRef {
            ptr: core::ptr::null(),
            len: GARBAGE_LEN
        }
        .as_slice()
        .is_empty());
        assert!(U8VecRef {
            ptr: core::ptr::null(),
            len: GARBAGE_LEN
        }
        .as_slice()
        .is_empty());
        assert!(F32VecRef {
            ptr: core::ptr::null(),
            len: GARBAGE_LEN
        }
        .as_slice()
        .is_empty());
        assert!(I32VecRef {
            ptr: core::ptr::null(),
            len: GARBAGE_LEN
        }
        .as_slice()
        .is_empty());
        // ...and the same for the mutable variants, through BOTH accessors.
        let mut m64 = GLint64VecRefMut {
            ptr: core::ptr::null_mut(),
            len: GARBAGE_LEN,
        };
        assert!(m64.as_slice().is_empty());
        assert!(m64.as_mut_slice().is_empty());
        let mut mf = GLfloatVecRefMut {
            ptr: core::ptr::null_mut(),
            len: GARBAGE_LEN,
        };
        assert!(mf.as_slice().is_empty());
        assert!(mf.as_mut_slice().is_empty());
        let mut mi = GLintVecRefMut {
            ptr: core::ptr::null_mut(),
            len: GARBAGE_LEN,
        };
        assert!(mi.as_slice().is_empty());
        assert!(mi.as_mut_slice().is_empty());
        let mut mb = GLbooleanVecRefMut {
            ptr: core::ptr::null_mut(),
            len: GARBAGE_LEN,
        };
        assert!(mb.as_slice().is_empty());
        assert!(mb.as_mut_slice().is_empty());
        let mut mu8 = U8VecRefMut {
            ptr: core::ptr::null_mut(),
            len: GARBAGE_LEN,
        };
        assert!(mu8.as_slice().is_empty());
        assert!(mu8.as_mut_slice().is_empty());
    }
    #[test]
    fn vec_refs_roundtrip_from_slice() {
        let u: [GLuint; 3] = [0, 1, u32::MAX];
        assert_eq!(GLuintVecRef::from(&u[..]).as_slice(), &u[..]);
        let e: [GLenum; 2] = [gl::TRIANGLES, u32::MAX];
        assert_eq!(GLenumVecRef::from(&e[..]).as_slice(), &e[..]);
        let b: [u8; 4] = [0, 127, 128, 255];
        assert_eq!(U8VecRef::from(&b[..]).as_slice(), &b[..]);
        let i: [i32; 3] = [i32::MIN, 0, i32::MAX];
        assert_eq!(I32VecRef::from(&i[..]).as_slice(), &i[..]);
        // Empty (but non-null) slices must also come back empty.
        let empty: [u8; 0] = [];
        assert!(U8VecRef::from(&empty[..]).as_slice().is_empty());
    }
    #[test]
    fn f32_vec_ref_preserves_nan_inf_and_subnormals_bit_exactly() {
        // These are pointer casts, not conversions: NaN payloads and -0.0 must
        // survive bit-for-bit (a NaN-normalizing copy would be a real bug).
        let vals: [f32; 6] = [
            f32::NAN,
            f32::INFINITY,
            f32::NEG_INFINITY,
            -0.0,
            f32::MIN_POSITIVE / 2.0, // subnormal
            f32::MAX,
        ];
        let r = F32VecRef::from(&vals[..]);
        let got = r.as_slice();
        assert_eq!(got.len(), vals.len());
        for (g, v) in got.iter().zip(vals.iter()) {
            assert_eq!(g.to_bits(), v.to_bits());
        }
        assert!(got[0].is_nan());
        assert!(got[3].is_sign_negative());
    }
    #[test]
    fn glint64_vec_ref_mut_handles_boundary_values() {
        let mut vals: [GLint64; 4] = [i64::MIN, -1, 0, i64::MAX];
        let mut r = GLint64VecRefMut::from(&mut vals[..]);
        assert_eq!(r.as_slice(), &[i64::MIN, -1, 0, i64::MAX]);
        // Writes through as_mut_slice must land in the caller's buffer.
        r.as_mut_slice()[0] = i64::MAX;
        r.as_mut_slice()[3] = i64::MIN;
        assert_eq!(vals, [i64::MAX, -1, 0, i64::MIN]);
    }
    #[test]
    fn mut_vec_refs_write_through_to_the_caller_buffer() {
        let mut floats: [GLfloat; 2] = [0.0, 0.0];
        GLfloatVecRefMut::from(&mut floats[..]).as_mut_slice()[1] = f32::NAN;
        assert!(floats[1].is_nan());
        let mut ints: [GLint; 2] = [0, 0];
        GLintVecRefMut::from(&mut ints[..]).as_mut_slice()[0] = i32::MIN;
        assert_eq!(ints[0], i32::MIN);
        let mut bools: [GLboolean; 2] = [0, 0];
        GLbooleanVecRefMut::from(&mut bools[..]).as_mut_slice()[1] = 255;
        assert_eq!(bools[1], 255);
        let mut bytes: [u8; 3] = [1, 2, 3];
        U8VecRefMut::from(&mut bytes[..]).as_mut_slice()[2] = 0;
        assert_eq!(bytes, [1, 2, 0]);
    }
    #[test]
    fn u8_vec_ref_ord_eq_hash_agree_with_the_underlying_slice() {
        use core::hash::{BuildHasher, Hasher};
        use alloc::collections::BTreeSet;
        let a = [1u8, 2, 3];
        let b = [1u8, 2, 4];
        let ra = U8VecRef::from(&a[..]);
        let rb = U8VecRef::from(&b[..]);
        assert_eq!(ra, U8VecRef::from(&a[..]));
        assert!(ra < rb);
        assert_eq!(ra.cmp(&rb), a[..].cmp(&b[..]));
        // A null ref must compare equal to an empty one (both are the empty slice).
        let null = U8VecRef {
            ptr: core::ptr::null(),
            len: 99,
        };
        let empty: [u8; 0] = [];
        assert_eq!(null, U8VecRef::from(&empty[..]));
        // Hash must be consistent with Eq (equal values -> equal hashes).
        fn hash_of(v: &U8VecRef) -> u64 {
            core::hash::BuildHasherDefault::<TestHasher>::default().hash_one(v)
        }
        #[derive(Default)]
        struct TestHasher(u64);
        impl Hasher for TestHasher {
            fn finish(&self) -> u64 {
                self.0
            }
            fn write(&mut self, bytes: &[u8]) {
                for b in bytes {
                    self.0 = self.0.wrapping_mul(31).wrapping_add(u64::from(*b));
                }
            }
        }
        assert_eq!(hash_of(&ra), hash_of(&U8VecRef::from(&a[..])));
        assert_eq!(hash_of(&null), hash_of(&U8VecRef::from(&empty[..])));
        // Ord must be a total order good enough for a BTreeSet.
        let mut set = BTreeSet::new();
        set.insert(ra.clone());
        set.insert(U8VecRef::from(&a[..]));
        set.insert(rb);
        assert_eq!(set.len(), 2);
    }
    #[test]
    fn refstr_vec_ref_roundtrips_and_maps_back_to_strs() {
        let strs = ["", "a", "\u{1F600}"];
        let refstrs: Vec<Refstr> = strs.iter().map(|s| Refstr::from(*s)).collect();
        let vec_ref = RefstrVecRef::from(&refstrs[..]);
        let got: Vec<&str> = vec_ref.as_slice().iter().map(Refstr::as_str).collect();
        assert_eq!(got, strs);
    }
    // =====================================================================
    // GLsyncPtr (constructor + round-trip)
    // =====================================================================
    #[test]
    fn glsync_ptr_roundtrips_null_and_nonnull() {
        let null = GLsyncPtr::new(core::ptr::null());
        assert!(null.clone().get().is_null());
        assert_eq!(alloc::format!("{null:?}"), "0x0");
        // A non-null (never dereferenced) sentinel must round-trip identically.
        let sentinel = usize::MAX as *const c_void;
        let p = GLsyncPtr::new(sentinel);
        assert_eq!(p.clone().get() as usize, usize::MAX);
        assert!(p.run_destructor);
    }
    // =====================================================================
    // shader_with_glsl_version (parser: the `#version` line swap)
    // =====================================================================
    #[cfg(feature = "std")]
    #[test]
    fn shader_with_glsl_version_replaces_only_the_first_line() {
        let src = b"#version 150\nbody line 1\nbody line 2";
        let out = shader_with_glsl_version(src, b"#version 300 es\n");
        assert_eq!(out, b"#version 300 es\nbody line 1\nbody line 2".to_vec());
    }
    #[cfg(feature = "std")]
    #[test]
    fn shader_with_glsl_version_empty_src_yields_just_the_version_line() {
        // No newline in src -> body_start = 0 (the map_or default), so the whole
        // (empty) src is kept and only the version line is prepended.
        assert_eq!(
            shader_with_glsl_version(b"", b"#version 150\n"),
            b"#version 150\n".to_vec()
        );
    }
    #[cfg(feature = "std")]
    #[test]
    fn shader_with_glsl_version_src_without_newline_is_kept_whole() {
        // A src with NO newline has no first line to strip: body_start stays 0,
        // so the version line is prepended and nothing is lost.
        let out = shader_with_glsl_version(b"void main(){}", b"#version 150\n");
        assert_eq!(out, b"#version 150\nvoid main(){}".to_vec());
    }
    #[cfg(feature = "std")]
    #[test]
    fn shader_with_glsl_version_leading_newline_drops_only_that_newline() {
        let out = shader_with_glsl_version(b"\nrest", b"V\n");
        assert_eq!(out, b"V\nrest".to_vec());
    }
    #[cfg(feature = "std")]
    #[test]
    fn shader_with_glsl_version_empty_version_line_still_strips_first_line() {
        let out = shader_with_glsl_version(b"#version 150\nbody", b"");
        assert_eq!(out, b"body".to_vec());
        // Both empty -> empty, no panic.
        assert!(shader_with_glsl_version(b"", b"").is_empty());
    }
    #[cfg(feature = "std")]
    #[test]
    fn shader_with_glsl_version_is_byte_exact_on_invalid_utf8_and_nul_bytes() {
        // Shader sources are bytes, not str: invalid UTF-8 / NULs must pass
        // through untouched (this is fed straight to glShaderSource).
        let src = b"#version 150\n\xFF\xFE\x00\x80body";
        let out = shader_with_glsl_version(src, b"#version 100\n");
        assert_eq!(out, b"#version 100\n\xFF\xFE\x00\x80body".to_vec());
        // A src that is ONLY invalid bytes with no newline is kept whole.
        let out2 = shader_with_glsl_version(&[0xFF, 0xFE, 0x00], b"V\n");
        assert_eq!(out2, vec![b'V', b'\n', 0xFF, 0xFE, 0x00]);
    }
    #[cfg(feature = "std")]
    #[test]
    fn shader_with_glsl_version_handles_a_1mb_source_without_hanging() {
        let mut src = b"#version 150\n".to_vec();
        src.resize(src.len() + 1_000_000, b'x');
        let out = shader_with_glsl_version(&src, b"#version 300 es\n");
        assert_eq!(out.len(), b"#version 300 es\n".len() + 1_000_000);
        assert!(out.starts_with(b"#version 300 es\n"));
        assert!(out.ends_with(b"xxxx"));
    }
    // =====================================================================
    // glsl_version_candidates (invariants the version probe relies on)
    // =====================================================================
    #[test]
    fn glsl_version_candidates_are_wellformed_and_distinct() {
        for gl_type in [GlType::Gl, GlType::Gles] {
            let candidates = glsl_version_candidates(gl_type);
            assert!(
                !candidates.is_empty(),
                "{gl_type:?} must have at least one candidate or the probe can never succeed"
            );
            for c in candidates {
                // GlContextPtr::new parses these back out with
                // `trim().trim_start_matches("#version ")`, which requires
                // valid UTF-8, the exact prefix, and a trailing newline.
                let s = core::str::from_utf8(c).expect("candidate must be valid UTF-8");
                assert!(s.starts_with("#version "), "{s:?}");
                assert!(s.ends_with('\n'), "{s:?}");
                let parsed = s.trim().trim_start_matches("#version ");
                assert!(!parsed.is_empty(), "{s:?} must parse to a nonempty version");
            }
            // No duplicates: a repeated candidate would just re-probe a known failure.
            for (i, a) in candidates.iter().enumerate() {
                for b in candidates.iter().skip(i + 1) {
                    assert_ne!(a, b, "duplicate candidate in {gl_type:?}");
                }
            }
        }
        // The GL and GLES lists must be disjoint (a GLES driver rejects `#version 150`).
        for g in glsl_version_candidates(GlType::Gl) {
            assert!(!glsl_version_candidates(GlType::Gles).contains(g));
        }
        // Documented examples from the API docs: "150" (GL) and "300 es" (GLES).
        let first_gl = core::str::from_utf8(glsl_version_candidates(GlType::Gl)[0]).unwrap();
        assert_eq!(first_gl.trim().trim_start_matches("#version "), "150");
        let first_es = core::str::from_utf8(glsl_version_candidates(GlType::Gles)[0]).unwrap();
        assert_eq!(first_es.trim().trim_start_matches("#version "), "300 es");
    }
    #[test]
    fn gl_type_from_context_gl_type_is_total() {
        assert_eq!(GlType::from(GlContextGlType::Gl), GlType::Gl);
        assert_eq!(GlType::from(GlContextGlType::GlEs), GlType::Gles);
    }
    // =====================================================================
    // GlContextPtr -- construction + the documented "unusable driver" fallback
    // =====================================================================
    #[test]
    fn gl_context_ptr_software_is_never_usable_and_has_no_shaders() {
        // Documented: "Always `false` for a Software context (which never
        // compiles these shaders)".
        let ctx = GlContextPtr::new(RendererType::Software, null_gl());
        assert!(!ctx.is_gl_usable());
        assert_eq!(ctx.get_svg_shader(), 0);
        assert_eq!(ctx.get_brush_shader(), 0);
        assert_eq!(ctx.get_fxaa_shader(), 0);
        assert_eq!(ctx.get_usable_glsl_version().as_str(), "");
        assert_eq!(ctx.renderer_type, RendererType::Software);
    }
    #[test]
    fn gl_context_ptr_hardware_with_broken_driver_falls_back_instead_of_panicking() {
        // THE contract `is_gl_usable()` exists for: context creation "succeeds"
        // but the driver compiles nothing. The probe must try every candidate
        // version, fail them all, and leave every program id at 0 -- so the
        // caller can fall back to CPU rendering -- rather than panicking or
        // handing out a garbage program id.
        let ctx = GlContextPtr::new(RendererType::Hardware, null_gl());
        assert!(
            !ctx.is_gl_usable(),
            "a driver that compiles nothing must report is_gl_usable() == false"
        );
        assert_eq!(ctx.get_svg_shader(), 0);
        assert_eq!(ctx.get_brush_shader(), 0);
        assert_eq!(ctx.get_fxaa_shader(), 0);
        assert_eq!(
            ctx.get_usable_glsl_version().as_str(),
            "",
            "glsl_version must be empty when the context is unusable"
        );
    }
    #[test]
    fn gl_context_ptr_get_type_defaults_to_desktop_gl_on_an_empty_version_string() {
        // get_type() sniffs the GL_VERSION string for "OpenGL ES"; a driver that
        // returns nothing must deterministically fall out as desktop Gl.
        assert_eq!(null_ctx().get_type(), GlType::Gl);
    }
    #[test]
    fn gl_context_ptr_clone_is_eq_but_distinct_contexts_are_not() {
        // Eq/Ord are identity-based (`as_usize` = the inner Rc address), so a
        // clone must compare equal and two independently created contexts must not.
        let a = null_ctx();
        let clone = a.clone();
        assert_eq!(a, clone);
        assert_eq!(a.cmp(&clone), core::cmp::Ordering::Equal);
        assert_eq!(a.partial_cmp(&clone), Some(core::cmp::Ordering::Equal));
        let b = null_ctx();
        assert_ne!(a, b);
        // Ord must be antisymmetric and consistent with PartialOrd.
        assert_eq!(a.cmp(&b), a.partial_cmp(&b).unwrap());
        assert_eq!(a.cmp(&b).reverse(), b.cmp(&a));
        // The clone must point at the same underlying GL context.
        assert!(Rc::ptr_eq(a.get(), clone.get()));
        assert!(!Rc::ptr_eq(a.get(), b.get()));
    }
    #[test]
    fn gl_context_ptr_gen_family_returns_empty_for_every_n_including_negatives() {
        // A driver that generates nothing returns an EMPTY list. Callers index
        // into this ([0] / .get(0).unwrap()), so the wrappers must at least not
        // panic on the way out -- and must not choke on a negative/extreme n.
        let ctx = null_ctx();
        for n in [0, 1, -1, i32::MIN, i32::MAX] {
            assert!(ctx.gen_buffers(n).as_slice().is_empty(), "gen_buffers({n})");
            assert!(ctx.gen_textures(n).as_slice().is_empty(), "gen_textures({n})");
            assert!(
                ctx.gen_framebuffers(n).as_slice().is_empty(),
                "gen_framebuffers({n})"
            );
            assert!(
                ctx.gen_renderbuffers(n).as_slice().is_empty(),
                "gen_renderbuffers({n})"
            );
            assert!(
                ctx.gen_vertex_arrays(n).as_slice().is_empty(),
                "gen_vertex_arrays({n})"
            );
            assert!(ctx.gen_queries(n).as_slice().is_empty(), "gen_queries({n})");
        }
    }
    #[test]
    fn gl_context_ptr_integer_extremes_do_not_panic() {
        // Offsets/sizes/limits at the boundary of their integer types. These are
        // passed straight through to GL, so the wrapper must not do arithmetic
        // that overflows on the way.
        let ctx = null_ctx();
        let data = [0u8; 4];
        let void_ptr = || GlVoidPtrConst {
            ptr: data.as_ptr().cast(),
            run_destructor: false,
        };
        for offset in [0isize, -1, isize::MIN, isize::MAX] {
            for size in [0isize, -1, isize::MIN, isize::MAX] {
                ctx.buffer_sub_data_untyped(gl::ARRAY_BUFFER, offset, size, void_ptr());
                let _ = ctx.map_buffer_range(gl::ARRAY_BUFFER, offset, size, 0);
            }
        }
        ctx.buffer_data_untyped(gl::ARRAY_BUFFER, isize::MIN, void_ptr(), gl::STATIC_DRAW);
        // usize offset at the extreme (reinterpreted as a byte offset pointer).
        for offset in [0usize, 1, usize::MAX] {
            ctx.tex_sub_image_2d_pbo(
                gl::TEXTURE_2D,
                0,
                0,
                0,
                1,
                1,
                gl::RGBA,
                gl::UNSIGNED_BYTE,
                offset,
            );
        }
        ctx.pixel_store_i(gl::PACK_ALIGNMENT, i32::MIN);
        ctx.pixel_store_i(gl::PACK_ALIGNMENT, i32::MAX);
        let _ = ctx.unmap_buffer(gl::ARRAY_BUFFER);
    }
    #[test]
    fn gl_context_ptr_float_extremes_do_not_panic() {
        // NaN / inf / subnormal floats must pass through the f32 wrappers untouched.
        let ctx = null_ctx();
        for v in [
            0.0,
            1.0,
            -1.0,
            f32::NAN,
            f32::INFINITY,
            f32::NEG_INFINITY,
            f32::MIN,
            f32::MAX,
        ] {
            ctx.sample_coverage(v, false);
            ctx.sample_coverage(v, true);
            ctx.polygon_offset(v, v);
        }
    }
    #[test]
    fn gl_context_ptr_shader_source_handles_empty_unicode_and_embedded_nuls() {
        // shader_source NUL-terminates each string itself; an already-embedded
        // NUL or multibyte UTF-8 must not panic on the way through.
        let ctx = null_ctx();
        let strings: StringVec = vec![
            AzString::from(String::new()),
            AzString::from("\u{1F600} emoji".to_string()),
            AzString::from("has\u{0}nul".to_string()),
            AzString::from("x".repeat(100_000)),
        ]
        .into();
        ctx.shader_source(0, strings);
        // An empty list of sources must also be fine.
        ctx.shader_source(u32::MAX, Vec::<AzString>::new().into());
    }
    #[test]
    fn gl_context_ptr_read_pixels_sizes_the_buffer_from_the_dimensions() {
        // NOTE: only SAFE (small, positive) dimensions are exercised here.
        // Negative dimensions are a live hazard in this path -- see the report:
        // gl-context-loader's read_pixels does
        //   `vec![0; width as usize * height as usize * bit_depth]`
        // BEFORE its null-pointer check, so a negative width sign-extends to
        // ~usize::MAX and the multiply overflows (debug) / requests an
        // exabyte-scale allocation (release). Deliberately not provoked.
        let ctx = null_ctx();
        let px = ctx.read_pixels(0, 0, 2, 3, gl::RGBA, gl::UNSIGNED_BYTE);
        assert_eq!(px.len(), 2 * 3 * 4, "RGBA/UNSIGNED_BYTE = 4 bytes per pixel");
        // A zero-size read is the degenerate-but-safe case.
        assert_eq!(
            ctx.read_pixels(0, 0, 0, 0, gl::RGBA, gl::UNSIGNED_BYTE).len(),
            0
        );
    }
    #[test]
    fn gl_context_ptr_read_pixels_into_buffer_accepts_null_and_undersized_targets() {
        // The destination is caller-owned here (no allocation from the dims), so
        // a null / empty / undersized target must simply no-op.
        let ctx = null_ctx();
        ctx.read_pixels_into_buffer(
            0,
            0,
            4,
            4,
            gl::RGBA,
            gl::UNSIGNED_BYTE,
            U8VecRefMut {
                ptr: core::ptr::null_mut(),
                len: 64,
            },
        );
        let mut small = [0u8; 1];
        ctx.read_pixels_into_buffer(
            0,
            0,
            4,
            4,
            gl::RGBA,
            gl::UNSIGNED_BYTE,
            (&mut small[..]).into(),
        );
        assert_eq!(small, [0u8; 1]);
    }
    #[test]
    fn gl_context_ptr_uniform_getters_accept_null_result_buffers() {
        let ctx = null_ctx();
        ctx.get_uniform_iv(
            0,
            -1,
            GLintVecRefMut {
                ptr: core::ptr::null_mut(),
                len: 16,
            },
        );
        ctx.get_uniform_fv(
            0,
            i32::MIN,
            GLfloatVecRefMut {
                ptr: core::ptr::null_mut(),
                len: 16,
            },
        );
        // ...and real buffers, at extreme locations.
        let mut ints = [0i32; 2];
        ctx.get_uniform_iv(u32::MAX, i32::MAX, (&mut ints[..]).into());
        let mut floats = [0f32; 2];
        ctx.get_uniform_fv(u32::MAX, i32::MAX, (&mut floats[..]).into());
    }
    #[test]
    fn gl_context_ptr_get_uniform_indices_handles_empty_and_null_name_lists() {
        let ctx = null_ctx();
        let empty: &[Refstr] = &[];
        assert!(ctx
            .get_uniform_indices(0, empty.into())
            .as_slice()
            .is_empty());
        assert!(ctx
            .get_uniform_indices(
                0,
                RefstrVecRef {
                    ptr: core::ptr::null(),
                    len: 4,
                },
            )
            .as_slice()
            .is_empty());
    }
    #[test]
    fn gl_context_ptr_delete_family_accepts_empty_and_null_id_lists() {
        // Deleting nothing must be a no-op, not an out-of-bounds read.
        let ctx = null_ctx();
        let empty: &[GLuint] = &[];
        ctx.delete_buffers(empty.into());
        ctx.delete_textures(empty.into());
        ctx.delete_framebuffers(empty.into());
        ctx.delete_renderbuffers(empty.into());
        ctx.delete_vertex_arrays(empty.into());
        ctx.delete_queries(empty.into());
        let null = || GLuintVecRef {
            ptr: core::ptr::null(),
            len: 7,
        };
        ctx.delete_buffers(null());
        ctx.delete_textures(null());
        ctx.delete_framebuffers(null());
        ctx.delete_renderbuffers(null());
        ctx.delete_vertex_arrays(null());
        ctx.delete_queries(null());
        // A real (bogus) id list, incl. 0 and u32::MAX, must also be fine.
        let ids: &[GLuint] = &[0, 1, u32::MAX];
        ctx.delete_buffers(ids.into());
        ctx.delete_textures(ids.into());
    }
    #[test]
    fn gl_context_ptr_draw_buffers_accepts_an_empty_list() {
        let ctx = null_ctx();
        let empty: &[GLenum] = &[];
        ctx.draw_buffers(empty.into());
        ctx.draw_buffers(
            GLenumVecRef {
                ptr: core::ptr::null(),
                len: 3,
            },
        );
    }
    // =====================================================================
    // VertexAttributeType / VertexAttribute / VertexLayout (numeric + invariants)
    // =====================================================================
    #[test]
    fn vertex_attribute_type_mem_size_matches_the_rust_type_it_names() {
        assert_eq!(
            VertexAttributeType::Float.get_mem_size(),
            core::mem::size_of::<f32>()
        );
        assert_eq!(
            VertexAttributeType::Double.get_mem_size(),
            core::mem::size_of::<f64>()
        );
        assert_eq!(
            VertexAttributeType::UnsignedByte.get_mem_size(),
            core::mem::size_of::<u8>()
        );
        assert_eq!(
            VertexAttributeType::UnsignedShort.get_mem_size(),
            core::mem::size_of::<u16>()
        );
        assert_eq!(
            VertexAttributeType::UnsignedInt.get_mem_size(),
            core::mem::size_of::<u32>()
        );
        // Every size must be nonzero -- a 0 would make get_stride() collapse to 0
        // and silently produce a degenerate vertex layout.
        for t in ALL_ATTRIB_TYPES {
            assert!(t.get_mem_size() > 0, "{t:?}");
        }
    }
    #[test]
    fn vertex_attribute_type_gl_ids_are_distinct_and_nonzero() {
        for (i, a) in ALL_ATTRIB_TYPES.iter().enumerate() {
            assert_ne!(a.get_gl_id(), 0, "{a:?} maps to the GL 'no type' id 0");
            for b in ALL_ATTRIB_TYPES.iter().skip(i + 1) {
                assert_ne!(
                    a.get_gl_id(),
                    b.get_gl_id(),
                    "{a:?} and {b:?} share a GL id -- one of them would upload as the wrong type"
                );
            }
        }
        assert_eq!(VertexAttributeType::Float.get_gl_id(), gl::FLOAT);
        assert_eq!(
            VertexAttributeType::UnsignedByte.get_gl_id(),
            gl::UNSIGNED_BYTE
        );
    }
    #[test]
    fn vertex_attribute_get_stride_at_zero_and_at_the_overflow_boundary() {
        let attr = |ty, item_count| VertexAttribute {
            va_name: AzString::from_const_str("vAttrXY"),
            layout_location: OptionUsize::None,
            attribute_type: ty,
            item_count,
        };
        // Zero items -> zero stride.
        for t in ALL_ATTRIB_TYPES {
            assert_eq!(attr(t, 0).get_stride(), 0, "{t:?}");
        }
        // Exact multiplication for representative counts.
        assert_eq!(attr(VertexAttributeType::Float, 2).get_stride(), 8);
        assert_eq!(attr(VertexAttributeType::Double, 4).get_stride(), 32);
        assert_eq!(attr(VertexAttributeType::UnsignedByte, 3).get_stride(), 3);
        // The LARGEST item_count that does not overflow `mem_size * item_count`.
        // (NOTE: get_stride() is a plain `*`, so an item_count above this bound
        // overflow-panics in debug / wraps in release -- see the report.)
        for t in ALL_ATTRIB_TYPES {
            let max_items = usize::MAX / t.get_mem_size();
            assert_eq!(
                attr(t, max_items).get_stride(),
                max_items * t.get_mem_size(),
                "{t:?} at the overflow boundary"
            );
        }
    }
    #[test]
    fn vertex_layout_stride_is_the_sum_of_its_fields() {
        let attr = |name: &str, ty, item_count| VertexAttribute {
            va_name: AzString::from(name.to_string()),
            layout_location: OptionUsize::None,
            attribute_type: ty,
            item_count,
        };
        // Empty layout: zero fields, zero total stride.
        let empty = VertexLayout {
            fields: VertexAttributeVec::from_const_slice(&[]),
        };
        assert_eq!(
            empty.fields.iter().map(VertexAttribute::get_stride).sum::<usize>(),
            0
        );
        // vec2<f32> + vec4<u8> = 8 + 4 = 12 bytes per vertex.
        let layout = VertexLayout {
            fields: vec![
                attr("vAttrXY", VertexAttributeType::Float, 2),
                attr("vColor", VertexAttributeType::UnsignedByte, 4),
            ]
            .into(),
        };
        let total: usize = layout.fields.iter().map(VertexAttribute::get_stride).sum();
        assert_eq!(total, 12);
        // Equality/Hash are structural, so an identical layout compares equal.
        assert_eq!(layout, layout.clone());
    }
    #[test]
    fn index_buffer_format_gl_ids_are_distinct() {
        // A collision here would silently draw the wrong primitive.
        for (i, a) in ALL_INDEX_FORMATS.iter().enumerate() {
            for b in ALL_INDEX_FORMATS.iter().skip(i + 1) {
                assert_ne!(a.get_gl_id(), b.get_gl_id(), "{a:?} vs {b:?}");
            }
        }
        assert_eq!(IndexBufferFormat::Points.get_gl_id(), gl::POINTS);
        assert_eq!(
            IndexBufferFormat::TriangleStrip.get_gl_id(),
            gl::TRIANGLE_STRIP
        );
    }
    // =====================================================================
    // Uniform / UniformType (NaN semantics feed GlShader::draw's dedupe)
    // =====================================================================
    #[test]
    fn uniform_type_nan_never_equals_itself_so_draw_always_reuploads_it() {
        // GlShader::draw skips re-setting a uniform when
        // `current_uniforms[i] != Some(uniform.uniform_type)`. With a NaN payload
        // that comparison is ALWAYS unequal, so a NaN uniform is re-uploaded on
        // every buffer -- correct (never stale), just not deduped. Pin it.
        let nan = UniformType::Float(f32::NAN);
        assert_ne!(nan, UniformType::Float(f32::NAN));
        assert_ne!(Some(nan), Some(nan));
        let nan_vec = UniformType::FloatVec4([f32::NAN, 0.0, 0.0, 0.0]);
        assert_ne!(nan_vec, nan_vec);
        // The flip side: +0.0 and -0.0 compare EQUAL, so a -0.0 -> +0.0 change is
        // deduped away and never re-uploaded. Harmless for a uniform, but pinned
        // so a change in semantics is visible.
        assert_eq!(UniformType::Float(0.0), UniformType::Float(-0.0));
        // Integer uniforms have no such wrinkle: they dedupe exactly.
        assert_eq!(UniformType::Int(i32::MIN), UniformType::Int(i32::MIN));
        assert_ne!(UniformType::Int(0), UniformType::UnsignedInt(0));
    }
    #[test]
    fn uniform_type_matrix_transpose_flag_is_part_of_its_identity() {
        let m = [0.0f32; 4];
        assert_ne!(
            UniformType::Matrix2 {
                transpose: false,
                matrix: m
            },
            UniformType::Matrix2 {
                transpose: true,
                matrix: m
            },
        );
        // Same payload, different arity -> different uniforms.
        assert_ne!(
            UniformType::FloatVec2([1.0, 2.0]),
            UniformType::IntVec2([1, 2])
        );
    }
    #[test]
    fn uniform_create_preserves_empty_unicode_and_huge_names() {
        let huge = "n".repeat(100_000);
        for name in ["", "u_color", "\u{1F600}", "e\u{0301}", huge.as_str()] {
            let u = Uniform::create(name.to_string(), UniformType::Int(0));
            assert_eq!(u.uniform_name.as_str(), name);
        }
    }
    // =====================================================================
    // GlShader -- the no-shader-compiler error path
    // =====================================================================
    #[test]
    fn gl_shader_new_reports_no_shader_compiler_instead_of_panicking() {
        // A driver that reports no shader compiler (GL_SHADER_COMPILER == FALSE)
        // must produce a clean Err -- for ANY source, including empty, garbage,
        // unicode and very large ones.
        let ctx = null_ctx();
        let huge = "x".repeat(200_000);
        for (vert, frag) in [
            ("", ""),
            ("   \t\n  ", "\n\n"),
            ("not glsl at all ;;;{{{", "\u{1F600}"),
            ("void main(){}", "void main(){}"),
            (huge.as_str(), huge.as_str()),
        ] {
            let err = GlShader::new(&ctx, vert, frag).unwrap_err();
            assert_eq!(err, GlShaderCreateError::NoShaderCompiler);
        }
    }
    #[test]
    fn shader_error_types_format_without_panicking_on_unicode_and_extremes() {
        let vert = VertexShaderCompileError {
            error_id: i32::MIN,
            info_log: AzString::from("\u{1F600} log".to_string()),
        };
        let frag = FragmentShaderCompileError {
            error_id: i32::MAX,
            info_log: AzString::from(String::new()),
        };
        let link = GlShaderLinkError {
            error_id: -1,
            info_log: AzString::from("multi\nline\0log".to_string()),
        };
        assert!(alloc::format!("{vert}").contains("-2147483648"));
        assert!(alloc::format!("{vert}").contains("\u{1F600}"));
        assert!(alloc::format!("{frag}").contains("2147483647"));
        let compile = GlShaderCompileError::Vertex(vert);
        assert!(alloc::format!("{compile}").contains("Failed to compile vertex shader"));
        // Debug delegates to Display -- must agree, and must not recurse.
        assert_eq!(alloc::format!("{compile:?}"), alloc::format!("{compile}"));
        let frag_err = GlShaderCompileError::Fragment(frag);
        assert!(alloc::format!("{frag_err}").contains("Failed to compile fragment shader"));
        let create = GlShaderCreateError::Link(link);
        assert!(alloc::format!("{create}").contains("Shader linking error"));
        assert_eq!(alloc::format!("{create:?}"), alloc::format!("{create}"));
        assert!(alloc::format!("{}", GlShaderCreateError::NoShaderCompiler)
            .contains("doesn't include a shader compiler"));
    }
    // =====================================================================
    // Texture -- getters, refcounting, and the degenerate-driver paths
    // =====================================================================
    #[test]
    fn texture_descriptor_mirrors_the_texture_including_extreme_sizes() {
        let tex = test_texture(42, 640, 480);
        let d = tex.get_descriptor();
        assert_eq!(d.width, 640);
        assert_eq!(d.height, 480);
        assert_eq!(d.format, RawImageFormat::RGBA8);
        assert_eq!(d.offset, 0);
        assert!(!d.flags.is_opaque);
        assert!(!d.flags.allow_mipmaps, "textures map 1:1, never mipmapped");
        // Zero-size and u32::MAX-size must not panic or wrap in the descriptor.
        let zero = test_texture(1, 0, 0);
        assert_eq!(zero.get_descriptor().width, 0);
        assert_eq!(zero.get_descriptor().height, 0);
        let huge = test_texture(1, u32::MAX, u32::MAX);
        assert_eq!(huge.get_descriptor().width, u32::MAX as usize);
        assert_eq!(huge.get_descriptor().height, u32::MAX as usize);
        // is_opaque must be carried through from the flags, not hardcoded.
        let opaque = Texture::create(
            7,
            TextureFlags {
                is_opaque: true,
                is_video_texture: true,
            },
            PhysicalSizeU32 {
                width: 2,
                height: 2,
            },
            ColorU {
                r: 0,
                g: 0,
                b: 0,
                a: 0,
            },
            null_ctx(),
            RawImageFormat::BGRA8,
        );
        assert!(opaque.get_descriptor().flags.is_opaque);
        assert_eq!(opaque.get_descriptor().format, RawImageFormat::BGRA8);
    }
    #[test]
    fn texture_clone_and_drop_share_one_refcount_without_double_freeing() {
        // Texture is refcounted through a raw Box<AtomicUsize>; a miscount here
        // is a double-free (or a leaked GL texture). Exercise fan-out + drop.
        let tex = test_texture(9, 4, 4);
        let clones: Vec<Texture> = (0..16).map(|_| tex.clone()).collect();
        for c in &clones {
            assert_eq!(c.texture_id, 9);
            assert_eq!(c.size, tex.size);
            assert_eq!(c.format, tex.format);
            assert!(c.run_destructor);
        }
        // Equality/Hash are by texture id.
        assert_eq!(clones[0], tex);
        assert_eq!(clones[0], clones[1]);
        assert_ne!(tex, test_texture(10, 4, 4));
        drop(clones); // 16 drops...
        drop(tex); // ...then the last one actually frees.
    }
    #[test]
    fn texture_display_and_debug_render_id_and_size() {
        let tex = test_texture(3, 16, 32);
        assert_eq!(alloc::format!("{tex}"), "Texture { id: 3, 16x32 }");
        // Debug delegates to Display.
        assert_eq!(alloc::format!("{tex:?}"), alloc::format!("{tex}"));
    }
    #[test]
    fn texture_paint_stroke_is_a_noop_when_gl_is_unusable() {
        // Documented: "No-op if the GL context is unusable". With no brush shader
        // (program id 0) this must bail out before touching GL -- including for
        // NaN / infinite / negative radii and coordinates, which must not produce
        // a huge dab loop. (`!(radius > 0.0)` is written that way precisely so it
        // also rejects NaN.)
        let mut tex = test_texture(5, 8, 8);
        assert_eq!(tex.gl_context.get_brush_shader(), 0);
        for radius in [1.0, 0.0, -1.0, f32::NAN, f32::INFINITY, f32::MAX] {
            let mut brush = Brush::new(
                ColorU {
                    r: 255,
                    g: 0,
                    b: 0,
                    a: 255,
                },
                radius,
            );
            brush.hardness = f32::NAN;
            brush.flow = f32::INFINITY;
            brush.spacing = 0.0; // would be a divide-by-zero step without the .max(0.01)
            tex.paint_stroke(f32::NAN, f32::NEG_INFINITY, f32::MAX, -0.0, brush);
            tex.paint_dot(f32::NAN, f32::NAN, brush);
        }
        // A zero-sized texture is the other early-out (tw/th <= 0).
        let mut zero = test_texture(6, 0, 0);
        zero.paint_dot(
            0.0,
            0.0,
            Brush::new(
                ColorU {
                    r: 1,
                    g: 1,
                    b: 1,
                    a: 1,
                },
                4.0,
            ),
        );
    }
    #[test]
    fn texture_copy_to_raw_image_returns_a_null_image_on_every_degenerate_input() {
        // The `w <= 0 || h <= 0` guard is load-bearing: size is a u32 but is cast
        // to i32 for glReadPixels, so a width >= 2^31 goes NEGATIVE. Without the
        // guard that reaches gl-context-loader's
        //   `vec![0; width as usize * height as usize * bit_depth]`
        // which sign-extends the negative width to ~usize::MAX -> overflow panic
        // (debug) / exabyte allocation (release). Prove the guard holds.
        let is_null_image = |img: &RawImage| img.width == 0 && img.height == 0;
        // texture id 0 -> null image
        assert!(is_null_image(&test_texture(0, 16, 16).copy_to_raw_image()));
        // zero size -> null image
        assert!(is_null_image(&test_texture(1, 0, 0).copy_to_raw_image()));
        assert!(is_null_image(&test_texture(1, 16, 0).copy_to_raw_image()));
        assert!(is_null_image(&test_texture(1, 0, 16).copy_to_raw_image()));
        // u32::MAX / 2^31 both cast to a NEGATIVE i32 and MUST be caught by the guard.
        assert!(is_null_image(
            &test_texture(1, u32::MAX, u32::MAX).copy_to_raw_image()
        ));
        assert!(is_null_image(
            &test_texture(1, 1 << 31, 1 << 31).copy_to_raw_image()
        ));
        // Valid id + valid size, but the driver hands back no framebuffer
        // (`fbo == 0`) -> still a clean null image, no unwrap panic.
        assert!(is_null_image(&test_texture(1, 4, 4).copy_to_raw_image()));
    }
    #[test]
    #[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
    fn texture_clear_panics_when_the_driver_allocates_no_framebuffer() {
        // DOCUMENTED panic: "Panics if no framebuffer/depthbuffer was allocated
        // (the GL object lists are empty)." A driver that generates nothing makes
        // gen_framebuffers(1) return an EMPTY list, so `.get(0).unwrap()` panics.
        // Pinned as documented behavior -- note that the sibling paths
        // (paint_stroke / copy_to_raw_image) degrade gracefully instead.
        test_texture(1, 4, 4).clear();
    }
    // =====================================================================
    // VertexArrayObject / VertexBuffer
    // =====================================================================
    #[test]
    fn vertex_array_object_clone_and_drop_share_one_refcount() {
        let layout = VertexLayout {
            fields: VertexAttributeVec::from_const_slice(&[]),
        };
        let vao = VertexArrayObject::new(layout, 77, null_ctx());
        assert_eq!(vao.vao_id, 77);
        assert!(vao.run_destructor);
        let clones: Vec<VertexArrayObject> = (0..8).map(|_| vao.clone()).collect();
        assert!(clones.iter().all(|c| c.vao_id == 77));
        assert_eq!(clones[0], vao);
        drop(clones);
        drop(vao);
    }
    #[test]
    fn vertex_buffer_new_raw_keeps_its_fields_and_refcounts_its_clones() {
        let vao = VertexArrayObject::new(
            VertexLayout {
                fields: VertexAttributeVec::from_const_slice(&[]),
            },
            1,
            null_ctx(),
        );
        let vb = VertexBuffer::new_raw(11, 300, vao, 22, 40, IndexBufferFormat::TriangleStrip);
        assert_eq!(vb.vertex_buffer_id, 11);
        assert_eq!(vb.vertex_buffer_len, 300);
        assert_eq!(vb.index_buffer_id, 22);
        assert_eq!(vb.index_buffer_len, 40);
        assert_eq!(vb.index_buffer_format, IndexBufferFormat::TriangleStrip);
        assert_eq!(
            alloc::format!("{vb}"),
            "VertexBuffer { buffer: 11 (length: 300) }"
        );
        // Eq/Hash are by vertex_buffer_id.
        let clones: Vec<VertexBuffer> = (0..8).map(|_| vb.clone()).collect();
        assert_eq!(clones[0], vb);
        drop(clones);
        drop(vb);
        // A zero-length buffer is degenerate but legal.
        let empty_vao = VertexArrayObject::new(
            VertexLayout {
                fields: VertexAttributeVec::from_const_slice(&[]),
            },
            0,
            null_ctx(),
        );
        let empty = VertexBuffer::new_raw(0, 0, empty_vao, 0, 0, IndexBufferFormat::Points);
        assert_eq!(empty.vertex_buffer_len, 0);
    }
    #[allow(dead_code)] // the field only exists to give the vertex a realistic size/layout
    struct TestVertex {
        _xy: [f32; 2],
    }
    impl VertexLayoutDescription for TestVertex {
        fn get_description() -> VertexLayout {
            VertexLayout {
                fields: vec![VertexAttribute {
                    va_name: AzString::from_const_str("vAttrXY"),
                    layout_location: OptionUsize::None,
                    attribute_type: VertexAttributeType::Float,
                    item_count: 2,
                }]
                .into(),
            }
        }
    }
    #[test]
    #[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
    fn vertex_buffer_new_panics_when_the_driver_allocates_no_vao() {
        // DOCUMENTED panic: "Panics if the GL driver failed to create the
        // vertex-array/buffer objects (the returned id lists are empty)."
        let verts = [TestVertex { _xy: [0.0, 0.0] }];
        let _ = VertexBuffer::new(
            null_ctx(),
            0,
            &verts[..],
            &[0u32],
            IndexBufferFormat::Triangles,
        );
    }
    // =====================================================================
    // The process-global GL texture table.
    //
    // ACTIVE_GL_TEXTURES is a `static mut` with no lock (GL is single-threaded
    // by design), and cargo test runs #[test]s on parallel threads. So ALL of
    // its coverage lives in this ONE test -- splitting it up would race the
    // table against itself. No other test in this crate touches it.
    // =====================================================================
    #[test]
    fn gl_texture_cache_insert_lookup_and_eviction_lifecycle() {
        let doc = DocumentId {
            namespace_id: crate::resources::IdNamespace(7),
            id: 1,
        };
        let other_doc = DocumentId {
            namespace_id: crate::resources::IdNamespace(7),
            id: 2,
        };
        // --- Empty / uninitialized table: every accessor must degrade, not panic.
        gl_textures_clear_opengl_cache();
        let stale = ExternalImageId { inner: u64::MAX };
        assert!(get_opengl_texture(&stale).is_none());
        assert!(
            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(0), &stale).is_none()
        );
        gl_textures_remove_epochs_from_pipeline(&doc, Epoch::from(0));
        gl_textures_remove_active_pipeline(&doc);
        gl_textures_clear_opengl_cache(); // idempotent
        // --- Insert into an UNINITIALIZED table.
        // NOTE: the doc comment on insert_into_active_gl_textures claims it
        // "Panics if the global active-GL-texture table has not been
        // initialized" -- it does not; it initializes the table itself. The doc
        // is stale (see the report). Pin the real behavior.
        let id5 = insert_into_active_gl_textures(doc, Epoch::from(5), test_texture(11, 4, 8));
        let id7 = insert_into_active_gl_textures(doc, Epoch::from(7), test_texture(22, 16, 32));
        assert_ne!(id5, id7, "each insert must mint a unique ExternalImageId");
        // --- Lookup: id -> (texture id, (w, h) as f32).
        assert_eq!(get_opengl_texture(&id5), Some((11, (4.0, 8.0))));
        assert_eq!(get_opengl_texture(&id7), Some((22, (16.0, 32.0))));
        assert!(get_opengl_texture(&stale).is_none());
        // A u32::MAX-sized texture goes through a lossy u32 -> f32 cast:
        // u32::MAX (2^32 - 1) has no exact f32, so it rounds UP to 2^32.
        let id_huge =
            insert_into_active_gl_textures(doc, Epoch::from(5), test_texture(33, u32::MAX, 1));
        assert_eq!(get_opengl_texture(&id_huge), Some((33, (4_294_967_296.0, 1.0))));
        // --- Epoch eviction is STRICTLY older-than: epoch 5 goes, epoch 7 stays.
        gl_textures_remove_epochs_from_pipeline(&doc, Epoch::from(7));
        assert!(get_opengl_texture(&id5).is_none(), "epoch 5 < 7 must be evicted");
        assert!(get_opengl_texture(&id_huge).is_none(), "epoch 5 < 7 must be evicted");
        assert_eq!(
            get_opengl_texture(&id7),
            Some((22, (16.0, 32.0))),
            "epoch 7 is NOT < 7, so it must survive"
        );
        // Evicting for an unknown document must be a no-op, not a panic.
        gl_textures_remove_epochs_from_pipeline(&other_doc, Epoch::from(u32::MAX));
        assert_eq!(get_opengl_texture(&id7), Some((22, (16.0, 32.0))));
        // --- remove_single: Some(()) when the (doc, epoch) path exists...
        assert_eq!(
            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(7), &id7),
            Some(())
        );
        assert!(get_opengl_texture(&id7).is_none());
        // ...including a second removal of an already-gone image (the path is
        // still there, so it reports Some(()) rather than None)...
        assert_eq!(
            remove_single_texture_from_active_gl_textures(&doc, &Epoch::from(7), &id7),
            Some(())
        );
        // ...but None when the document or the epoch is unknown.
        assert!(
            remove_single_texture_from_active_gl_textures(&other_doc, &Epoch::from(7), &id7)
                .is_none()
        );
        assert!(remove_single_texture_from_active_gl_textures(
            &doc,
            &Epoch::from(u32::MAX),
            &id7
        )
        .is_none());
        // --- remove_active_pipeline drops the whole document.
        let id_a = insert_into_active_gl_textures(doc, Epoch::from(1), test_texture(44, 2, 2));
        let id_b = insert_into_active_gl_textures(other_doc, Epoch::from(1), test_texture(55, 2, 2));
        assert!(get_opengl_texture(&id_a).is_some());
        gl_textures_remove_active_pipeline(&doc);
        assert!(get_opengl_texture(&id_a).is_none(), "doc was removed");
        assert!(
            get_opengl_texture(&id_b).is_some(),
            "other_doc must be untouched"
        );
        // --- clear drops everything.
        gl_textures_clear_opengl_cache();
        assert!(get_opengl_texture(&id_b).is_none());
        // Leave the table clean for anything that runs after us.
        gl_textures_clear_opengl_cache();
    }
}