Skip to main content

gasm_host/
gl_backend.rs

1//! The GL backend of gasm:gl natively: ANGLE, executing the calls the model let
2//! through (runners/web/lib/gl.js does the same with WebGL 2). The model has done the
3//! checks every runner shares and answered the questions every runner answers the
4//! same way (bindings, the pixel store, query and sync readiness); this part keeps the
5//! model's names → GL objects, turns guest offsets into pointers and asks GL the rest.
6
7use std::collections::HashMap;
8use std::ffi::{c_char, c_void};
9
10use wasmtime::Val;
11
12use super::{guest_slice, guest_slice_mut, guest_str, text, Gl, Kind, PIXEL_PACK_BUFFER, PIXEL_UNPACK_BUFFER};
13use crate::angle::{c_string, Angle};
14
15/// WebGL extension names and the GLES extensions ANGLE enables for them.
16const EXTENSIONS: &[(&str, &[&str])] = &[
17    ("EXT_color_buffer_float", &["GL_EXT_color_buffer_float"]),
18    ("EXT_color_buffer_half_float", &["GL_EXT_color_buffer_half_float"]),
19    ("EXT_float_blend", &["GL_EXT_float_blend"]),
20    ("EXT_texture_filter_anisotropic", &["GL_EXT_texture_filter_anisotropic"]),
21    ("OES_texture_float_linear", &["GL_OES_texture_float_linear"]),
22    (
23        "WEBGL_compressed_texture_s3tc",
24        &["GL_EXT_texture_compression_dxt1", "GL_ANGLE_texture_compression_dxt3", "GL_ANGLE_texture_compression_dxt5"],
25    ),
26    ("WEBGL_compressed_texture_s3tc_srgb", &["GL_EXT_texture_compression_s3tc_srgb"]),
27    ("EXT_texture_compression_bptc", &["GL_EXT_texture_compression_bptc"]),
28    ("EXT_texture_compression_rgtc", &["GL_EXT_texture_compression_rgtc"]),
29    ("WEBGL_compressed_texture_etc", &["GL_ANGLE_compressed_texture_etc"]),
30    ("WEBGL_compressed_texture_astc", &["GL_KHR_texture_compression_astc_ldr"]),
31];
32
33const GL_EXTENSIONS: u32 = 0x1F03;
34const GL_NUM_EXTENSIONS: u32 = 0x821D;
35const GL_REQUESTABLE_EXTENSIONS_ANGLE: u32 = 0x93A8;
36const INVALID_ENUM: u32 = 0x0500;
37const INVALID_VALUE: u32 = 0x0501;
38
39/// ANGLE and the tables that connect the model to it.
40pub struct Backend {
41    pub angle: Angle,
42    /// (kind, model name) → GL name, and back
43    names: HashMap<(Kind, u32), u32>,
44    rev: HashMap<(Kind, u32), u32>,
45    syncs: HashMap<u32, usize>,
46    /// (program, model location) → (GL location, components per element)
47    uniforms: HashMap<(u32, i32), (i32, u32)>,
48    /// the guest called present() this frame
49    presented: bool,
50}
51
52impl Backend {
53    pub fn new(angle: Angle) -> Backend {
54        Backend { angle, names: HashMap::new(), rev: HashMap::new(), syncs: HashMap::new(), uniforms: HashMap::new(), presented: false }
55    }
56    fn obj(&self, k: Kind, n: u32) -> u32 {
57        if n == 0 { 0 } else { self.names.get(&(k, n)).copied().unwrap_or(0) }
58    }
59    fn model_name(&self, k: Kind, gl: u32) -> u32 {
60        if gl == 0 { 0 } else { self.rev.get(&(k, gl)).copied().unwrap_or(0) }
61    }
62    fn put_name(&mut self, k: Kind, n: u32, gl: u32) {
63        self.names.insert((k, n), gl);
64        self.rev.insert((k, gl), n);
65    }
66    fn take(&mut self, k: Kind, n: u32) -> Option<u32> {
67        let gl = self.names.remove(&(k, n))?;
68        self.rev.remove(&(k, gl));
69        Some(gl)
70    }
71    /// The GLES extensions ANGLE has or can enable.
72    fn gl_extensions(&self) -> Vec<String> {
73        let g = &self.angle.gl;
74        let mut out = Vec::new();
75        unsafe {
76            let mut n = 0;
77            (g.glGetIntegerv)(GL_NUM_EXTENSIONS, &mut n as *mut i32 as *mut c_void);
78            for i in 0..n.max(0) as u32 {
79                out.push(cstr((g.glGetStringi)(GL_EXTENSIONS, i)));
80            }
81            out.extend(cstr((g.glGetString)(GL_REQUESTABLE_EXTENSIONS_ANGLE)).split_whitespace().map(str::to_owned));
82        }
83        out
84    }
85    /// The WebGL extensions available (what get_string(GL_EXTENSIONS) lists, as in browsers).
86    fn webgl_extensions(&self) -> Vec<&'static str> {
87        let have = self.gl_extensions();
88        EXTENSIONS.iter().filter(|(_, gl)| gl.iter().all(|e| have.iter().any(|h| h == e))).map(|(w, _)| *w).collect()
89    }
90    fn enable_extension(&self, name: &str) -> bool {
91        let Some((_, gl)) = EXTENSIONS.iter().find(|(w, _)| *w == name) else { return false };
92        let have = self.gl_extensions();
93        if !gl.iter().all(|e| have.iter().any(|h| h == e)) {
94            return false;
95        }
96        for e in *gl {
97            let c = c_string(e);
98            unsafe { (self.angle.gl.glRequestExtensionANGLE)(c.as_ptr() as *const c_void) };
99        }
100        true
101    }
102    /// Move GL's pending errors into the model's queue (in order), so a query's own
103    /// error can be told apart; returns the last one.
104    fn drain(&self, model: &mut Gl) -> u32 {
105        let mut last = 0;
106        loop {
107            let e = unsafe { (self.angle.gl.glGetError)() };
108            if e == 0 {
109                return last;
110            }
111            model.error(e);
112            last = e;
113        }
114    }
115    fn int(&self, f: impl FnOnce(*mut c_void)) -> i32 {
116        let mut v = 0i32;
117        f(&mut v as *mut i32 as *mut c_void);
118        v
119    }
120    fn info_log(&self, shader: bool, obj: u32) -> String {
121        let g = &self.angle.gl;
122        unsafe {
123            let len = self.int(|p| if shader { (g.glGetShaderiv)(obj, 0x8B84, p) } else { (g.glGetProgramiv)(obj, 0x8B84, p) });
124            if len <= 0 {
125                return String::new();
126            }
127            let mut buf = vec![0u8; len as usize];
128            let mut got = 0;
129            if shader {
130                (g.glGetShaderInfoLog)(obj, len, &mut got as *mut i32 as *mut c_void, buf.as_mut_ptr() as *mut c_void);
131            } else {
132                (g.glGetProgramInfoLog)(obj, len, &mut got as *mut i32 as *mut c_void, buf.as_mut_ptr() as *mut c_void);
133            }
134            buf.truncate(got.max(0) as usize);
135            String::from_utf8_lossy(&buf).into_owned()
136        }
137    }
138    /// Components per element of the uniform `name` in `program` (for get_uniform*).
139    fn uniform_components(&self, program: u32, name: &str) -> u32 {
140        let g = &self.angle.gl;
141        let base = name.split('[').next().unwrap_or(name);
142        unsafe {
143            let n = self.int(|p| (g.glGetProgramiv)(program, 0x8B86, p));
144            let max = self.int(|p| (g.glGetProgramiv)(program, 0x8B87, p)).max(1);
145            for i in 0..n.max(0) as u32 {
146                let mut buf = vec![0u8; max as usize];
147                let (mut len, mut size, mut ty) = (0i32, 0i32, 0u32);
148                (g.glGetActiveUniform)(
149                    program, i, max, &mut len as *mut i32 as *mut c_void, &mut size as *mut i32 as *mut c_void,
150                    &mut ty as *mut u32 as *mut c_void, buf.as_mut_ptr() as *mut c_void,
151                );
152                let u = String::from_utf8_lossy(&buf[..len.max(0) as usize]).into_owned();
153                if u == name || u.split('[').next() == Some(base) {
154                    return type_components(ty);
155                }
156            }
157        }
158        16
159    }
160}
161
162/// Components of a GLSL uniform type (samplers are one int).
163fn type_components(ty: u32) -> u32 {
164    match ty {
165        0x8B50 | 0x8B53 | 0x8DC6 | 0x8B57 => 2, // vec2 ivec2 uvec2 bvec2
166        0x8B51 | 0x8B54 | 0x8DC7 | 0x8B58 => 3,
167        0x8B52 | 0x8B55 | 0x8DC8 | 0x8B59 | 0x8B5A => 4, // + mat2
168        0x8B5B => 9,
169        0x8B5C => 16,
170        0x8B65 | 0x8B66 => 6, // mat2x3 mat2x4: 6, 8
171        0x8B67 | 0x8B68 => 8,
172        0x8B69 | 0x8B6A => 12,
173        _ => 1,
174    }
175}
176
177fn cstr(p: *const c_void) -> String {
178    if p.is_null() {
179        return String::new();
180    }
181    unsafe { std::ffi::CStr::from_ptr(p as *const c_char) }.to_string_lossy().into_owned()
182}
183
184/// How many values a glGet* of `pname` returns.
185fn param_count(g: &crate::gles::Gles, pname: u32) -> usize {
186    match pname {
187        0x0D3A | 0x846D | 0x846E | 0x0B70 => 2,
188        0x0BA2 | 0x0C10 | 0x0C22 | 0x0C23 | 0x8005 => 4,
189        0x86A3 => {
190            let mut n = 0i32;
191            unsafe { (g.glGetIntegerv)(0x86A2, &mut n as *mut i32 as *mut c_void) };
192            n.max(0) as usize
193        }
194        _ => 1,
195    }
196}
197
198fn put_f(mem: &mut [u8], dst: u32, count: u32, v: &[f32]) -> wasmtime::Result<i32> {
199    let n = v.len().min(count as usize);
200    if n > 0 {
201        let out = guest_slice_mut(mem, dst, n as u64 * 4)?;
202        for (i, x) in v[..n].iter().enumerate() {
203            out[i * 4..i * 4 + 4].copy_from_slice(&x.to_le_bytes());
204        }
205    }
206    Ok(v.len() as i32)
207}
208
209fn put_i(mem: &mut [u8], dst: u32, count: u32, v: &[i64], wide: bool) -> wasmtime::Result<i32> {
210    let n = v.len().min(count as usize);
211    let size = if wide { 8 } else { 4 };
212    if n > 0 {
213        let out = guest_slice_mut(mem, dst, (n * size) as u64)?;
214        for (i, x) in v[..n].iter().enumerate() {
215            if wide {
216                out[i * 8..i * 8 + 8].copy_from_slice(&x.to_le_bytes());
217            } else {
218                out[i * 4..i * 4 + 4].copy_from_slice(&(*x as i32).to_le_bytes());
219            }
220        }
221    }
222    Ok(v.len() as i32)
223}
224
225impl Gl {
226    /// Execute calls on ANGLE from now on (the model stays in front of it).
227    pub fn attach(&mut self, angle: Angle) {
228        self.backend = Some(Box::new(Backend::new(angle)));
229    }
230    pub fn has_backend(&self) -> bool {
231        self.backend.is_some()
232    }
233    /// The frame ended: show it if `show` (unless the guest did with present()).
234    pub fn end_frame(&mut self, show: bool) {
235        if let Some(b) = &mut self.backend {
236            if show && !b.presented {
237                b.angle.swap();
238            }
239            b.presented = false;
240        }
241    }
242    /// Show an image instead of the game's frame, leaving the game's GL state as it
243    /// was (the consent question). False without ANGLE.
244    pub fn show_over_game(&self, rgba: &[u8], w: u32, h: u32) -> bool {
245        self.backend.as_ref().map(|b| b.angle.show_image_over_game(rgba, w, h)).is_some()
246    }
247    /// The default framebuffer as RGBA8, top to bottom (headless screenshots).
248    pub fn read_frame(&self) -> Option<(u32, u32, Vec<u8>)> {
249        self.backend.as_ref().map(|b| b.angle.read_frame())
250    }
251    /// The drawable size, if a backend draws.
252    pub fn size(&self) -> Option<(u32, u32)> {
253        self.backend.as_ref().map(|b| b.angle.size())
254    }
255    pub fn renderer(&self) -> Option<&str> {
256        self.backend.as_ref().map(|b| b.angle.renderer.as_str())
257    }
258}
259
260/// Execute one call the model passed. `results` holds the model's answer, which
261/// stays unless GL has the real one.
262pub(super) fn forward(
263    be: &mut Backend,
264    model: &mut Gl,
265    name: &str,
266    args: &[Val],
267    results: &mut [Val],
268    mem: &mut [u8],
269) -> wasmtime::Result<()> {
270    let a = |i: usize| -> i32 {
271        match args[i] {
272            Val::I32(v) => v,
273            Val::I64(v) => v as i32,
274            _ => 0,
275        }
276    };
277    let u = |i: usize| a(i) as u32;
278    let f = |i: usize| match args[i] {
279        Val::F32(b) => f32::from_bits(b),
280        _ => 0.0,
281    };
282    let model_result = match results.first() {
283        Some(Val::I32(v)) => *v as i64,
284        _ => 0,
285    };
286    let mut ret = |v: i64| {
287        if let Some(r) = results.first_mut() {
288            *r = Val::I32(v as i32);
289        }
290    };
291    let base = mem.as_mut_ptr();
292    let at = |p: u32| -> *const c_void { if p == 0 { std::ptr::null() } else { unsafe { base.add(p as usize) as *const c_void } } };
293    let at_mut = |p: u32| -> *mut c_void { unsafe { base.add(p as usize) as *mut c_void } };
294    let off = |o: u32| o as usize as *const c_void;
295    let g = &be.angle.gl as *const crate::gles::Gles;
296    let g = unsafe { &*g };
297    use Kind::*;
298    unsafe {
299        match name {
300            // ---- frames, context ----
301            "width" => ret(be.angle.size().0 as i64),
302            "height" => ret(be.angle.size().1 as i64),
303            "present" => {
304                be.angle.swap();
305                be.presented = true;
306            }
307            "get_error" => {
308                if model_result == 0 {
309                    ret((g.glGetError)() as i64)
310                }
311            }
312            "get_string" => {
313                let s = if u(0) == GL_EXTENSIONS { be.webgl_extensions().join(" ") } else { cstr((g.glGetString)(u(0))) };
314                ret(text(mem, u(1), u(2), &s)? as i64)
315            }
316            "enable_extension" => {
317                let n = guest_str(mem, u(0), u(1))?;
318                ret(be.enable_extension(&n) as i64)
319            }
320            "get_integerv" | "get_floatv" | "get_integer64v" => {
321                let pname = u(0);
322                if model.model_param(pname).is_some() {
323                    return Ok(());
324                }
325                be.drain(model);
326                let n = param_count(g, pname).max(1);
327                let (dst, count) = (u(1), u(2));
328                match name {
329                    "get_floatv" => {
330                        let mut v = vec![0f32; n.max(16)];
331                        (g.glGetFloatv)(pname, v.as_mut_ptr() as *mut c_void);
332                        if (g.glGetError)() == INVALID_ENUM {
333                            model.error(INVALID_ENUM);
334                            {
335                    ret(-1);
336                    return Ok(());
337                }
338                        }
339                        v.truncate(n);
340                        ret(put_f(mem, dst, count, &v)? as i64)
341                    }
342                    _ => {
343                        let mut v = vec![0i64; n.max(16)];
344                        (g.glGetInteger64v)(pname, v.as_mut_ptr() as *mut c_void);
345                        if (g.glGetError)() == INVALID_ENUM {
346                            model.error(INVALID_ENUM);
347                            {
348                    ret(-1);
349                    return Ok(());
350                }
351                        }
352                        v.truncate(n);
353                        // objects come back as the guest's names
354                        match pname {
355                            0x8919 => v[0] = be.model_name(Sampler, v[0] as u32) as i64,
356                            0x8E25 => v[0] = be.model_name(TransformFeedback, v[0] as u32) as i64,
357                            _ => {}
358                        }
359                        ret(put_i(mem, dst, count, &v, name == "get_integer64v")? as i64)
360                    }
361                }
362            }
363            "get_integeri_v" => {
364                be.drain(model);
365                let mut v = 0i64;
366                (g.glGetInteger64i_v)(u(0), u(1), &mut v as *mut i64 as *mut c_void);
367                if (g.glGetError)() != 0 {
368                    model.error(INVALID_ENUM);
369                    {
370                    ret(-1);
371                    return Ok(());
372                }
373                }
374                if u(0) == 0x8A28 || u(0) == 0x8C8F {
375                    v = be.model_name(Buffer, v as u32) as i64;
376                }
377                ret(put_i(mem, u(2), u(3), &[v], false)? as i64)
378            }
379            "get_internalformativ" => {
380                let (t, fmt, pname) = (u(0), u(1), u(2));
381                let n = if pname == 0x80A9 { be.int(|p| (g.glGetInternalformativ)(t, fmt, 0x9380, 1, p)).max(0) as usize } else { 1 };
382                let mut v = vec![0i32; n.max(1)];
383                (g.glGetInternalformativ)(t, fmt, pname, n as i32, v.as_mut_ptr() as *mut c_void);
384                v.truncate(n);
385                let v: Vec<i64> = v.into_iter().map(i64::from).collect();
386                ret(put_i(mem, u(3), u(4), &v, false)? as i64)
387            }
388            "get_shader_precision_format" => {
389                let (mut range, mut precision) = ([0i32; 2], 0i32);
390                (g.glGetShaderPrecisionFormat)(u(0), u(1), range.as_mut_ptr() as *mut c_void, &mut precision as *mut i32 as *mut c_void);
391                put_i(mem, u(2), 3, &[range[0] as i64, range[1] as i64, precision as i64], false)?;
392            }
393
394            // ---- state ----
395            "active_texture" => (g.glActiveTexture)(u(0)),
396            "blend_color" => (g.glBlendColor)(f(0), f(1), f(2), f(3)),
397            "blend_equation" => (g.glBlendEquation)(u(0)),
398            "blend_equation_separate" => (g.glBlendEquationSeparate)(u(0), u(1)),
399            "blend_func" => (g.glBlendFunc)(u(0), u(1)),
400            "blend_func_separate" => (g.glBlendFuncSeparate)(u(0), u(1), u(2), u(3)),
401            "clear" => (g.glClear)(u(0)),
402            "clear_color" => (g.glClearColor)(f(0), f(1), f(2), f(3)),
403            "clear_depthf" => (g.glClearDepthf)(f(0)),
404            "clear_stencil" => (g.glClearStencil)(a(0)),
405            "color_mask" => (g.glColorMask)((u(0) != 0) as u8, (u(1) != 0) as u8, (u(2) != 0) as u8, (u(3) != 0) as u8),
406            "cull_face" => (g.glCullFace)(u(0)),
407            "depth_func" => (g.glDepthFunc)(u(0)),
408            "depth_mask" => (g.glDepthMask)((u(0) != 0) as u8),
409            "depth_rangef" => (g.glDepthRangef)(f(0), f(1)),
410            "disable" => (g.glDisable)(u(0)),
411            "enable" => (g.glEnable)(u(0)),
412            "is_enabled" => ret((g.glIsEnabled)(u(0)) as i64),
413            "front_face" => (g.glFrontFace)(u(0)),
414            "hint" => (g.glHint)(u(0), u(1)),
415            "line_width" => (g.glLineWidth)(f(0)),
416            "pixel_storei" => {
417                // WebGL-only pnames (UNPACK_FLIP_Y_WEBGL, ...) have no GLES equivalent
418                if !(0x9240..=0x9244).contains(&u(0)) {
419                    (g.glPixelStorei)(u(0), a(1))
420                }
421            }
422            "polygon_offset" => (g.glPolygonOffset)(f(0), f(1)),
423            "sample_coverage" => (g.glSampleCoverage)(f(0), (u(1) != 0) as u8),
424            "scissor" => (g.glScissor)(a(0), a(1), a(2), a(3)),
425            "viewport" => (g.glViewport)(a(0), a(1), a(2), a(3)),
426            "stencil_func" => (g.glStencilFunc)(u(0), a(1), u(2)),
427            "stencil_func_separate" => (g.glStencilFuncSeparate)(u(0), u(1), a(2), u(3)),
428            "stencil_mask" => (g.glStencilMask)(u(0)),
429            "stencil_mask_separate" => (g.glStencilMaskSeparate)(u(0), u(1)),
430            "stencil_op" => (g.glStencilOp)(u(0), u(1), u(2)),
431            "stencil_op_separate" => (g.glStencilOpSeparate)(u(0), u(1), u(2), u(3)),
432            "finish" => (g.glFinish)(),
433            "flush" => (g.glFlush)(),
434
435            // ---- object lifetimes (the model made or retired the name) ----
436            "create_buffer" | "create_texture" | "create_vertex_array" | "create_sampler" | "create_framebuffer"
437            | "create_renderbuffer" | "create_query" | "create_transform_feedback" => {
438                let (kind, make): (Kind, unsafe extern "system" fn(i32, *mut c_void)) = match name {
439                    "create_buffer" => (Buffer, g.glGenBuffers),
440                    "create_texture" => (Texture, g.glGenTextures),
441                    "create_vertex_array" => (VertexArray, g.glGenVertexArrays),
442                    "create_sampler" => (Sampler, g.glGenSamplers),
443                    "create_framebuffer" => (Framebuffer, g.glGenFramebuffers),
444                    "create_renderbuffer" => (Renderbuffer, g.glGenRenderbuffers),
445                    "create_query" => (Query, g.glGenQueries),
446                    _ => (TransformFeedback, g.glGenTransformFeedbacks),
447                };
448                let mut x = 0u32;
449                make(1, &mut x as *mut u32 as *mut c_void);
450                be.put_name(kind, model_result as u32, x);
451            }
452            "delete_buffer" | "delete_texture" | "delete_vertex_array" | "delete_sampler" | "delete_framebuffer"
453            | "delete_renderbuffer" | "delete_query" | "delete_transform_feedback" => {
454                let (kind, del): (Kind, unsafe extern "system" fn(i32, *const c_void)) = match name {
455                    "delete_buffer" => (Buffer, g.glDeleteBuffers),
456                    "delete_texture" => (Texture, g.glDeleteTextures),
457                    "delete_vertex_array" => (VertexArray, g.glDeleteVertexArrays),
458                    "delete_sampler" => (Sampler, g.glDeleteSamplers),
459                    "delete_framebuffer" => (Framebuffer, g.glDeleteFramebuffers),
460                    "delete_renderbuffer" => (Renderbuffer, g.glDeleteRenderbuffers),
461                    "delete_query" => (Query, g.glDeleteQueries),
462                    _ => (TransformFeedback, g.glDeleteTransformFeedbacks),
463                };
464                if let Some(x) = be.take(kind, u(0)) {
465                    del(1, &x as *const u32 as *const c_void);
466                }
467            }
468            "create_shader" => {
469                let x = (g.glCreateShader)(u(0));
470                be.put_name(Shader, model_result as u32, x);
471            }
472            "create_program" => {
473                let x = (g.glCreateProgram)();
474                be.put_name(Program, model_result as u32, x);
475            }
476            "delete_shader" => {
477                if let Some(x) = be.take(Shader, u(0)) {
478                    (g.glDeleteShader)(x)
479                }
480            }
481            "delete_program" => {
482                be.uniforms.retain(|(p, _), _| *p != u(0));
483                if let Some(x) = be.take(Program, u(0)) {
484                    (g.glDeleteProgram)(x)
485                }
486            }
487            "fence_sync" => {
488                let s = (g.glFenceSync)(u(0), u(1));
489                be.syncs.insert(model_result as u32, s as usize);
490            }
491            "delete_sync" => {
492                if let Some(s) = be.syncs.remove(&u(0)) {
493                    (g.glDeleteSync)(s as *const c_void)
494                }
495            }
496            // the model answers these (names, readiness)
497            "is_buffer" | "is_texture" | "is_vertex_array" | "is_sampler" | "is_framebuffer" | "is_renderbuffer"
498            | "is_shader" | "is_program" | "is_query" | "is_sync" | "is_transform_feedback" | "frame_shown"
499            | "get_shader_source" | "get_queryiv" | "client_wait_sync" | "wait_sync" | "get_synciv" => {}
500
501            // ---- buffers ----
502            "bind_buffer" => (g.glBindBuffer)(u(0), be.obj(Buffer, u(1))),
503            "bind_buffer_base" => (g.glBindBufferBase)(u(0), u(1), be.obj(Buffer, u(2))),
504            "bind_buffer_range" => (g.glBindBufferRange)(u(0), u(1), be.obj(Buffer, u(2)), u(3) as isize, u(4) as isize),
505            "buffer_data" => (g.glBufferData)(u(0), u(2) as isize, at(u(1)), u(3)),
506            "buffer_sub_data" => (g.glBufferSubData)(u(0), u(1) as isize, u(3) as isize, at(u(2))),
507            "copy_buffer_sub_data" => (g.glCopyBufferSubData)(u(0), u(1), u(2) as isize, u(3) as isize, u(4) as isize),
508            "get_buffer_sub_data" => {
509                // GLES reads buffers back through a mapping
510                let (t, o, dst, len) = (u(0), u(1), u(2), u(3));
511                if len > 0 {
512                    let p = (g.glMapBufferRange)(t, o as isize, len as isize, 0x0001);
513                    if !p.is_null() {
514                        let out = guest_slice_mut(mem, dst, len as u64)?;
515                        std::ptr::copy_nonoverlapping(p as *const u8, out.as_mut_ptr(), len as usize);
516                        (g.glUnmapBuffer)(t);
517                    }
518                }
519            }
520            "get_buffer_parameteriv" => ret(be.int(|p| (g.glGetBufferParameteriv)(u(0), u(1), p)) as i64),
521
522            // ---- vertex arrays ----
523            "bind_vertex_array" => (g.glBindVertexArray)(be.obj(VertexArray, u(0))),
524            "enable_vertex_attrib_array" => (g.glEnableVertexAttribArray)(u(0)),
525            "disable_vertex_attrib_array" => (g.glDisableVertexAttribArray)(u(0)),
526            "vertex_attrib_pointer" => (g.glVertexAttribPointer)(u(0), a(1), u(2), (u(3) != 0) as u8, a(4), off(u(5))),
527            "vertex_attrib_ipointer" => (g.glVertexAttribIPointer)(u(0), a(1), u(2), a(3), off(u(4))),
528            "vertex_attrib_divisor" => (g.glVertexAttribDivisor)(u(0), u(1)),
529            "vertex_attrib4f" => (g.glVertexAttrib4f)(u(0), f(1), f(2), f(3), f(4)),
530            "vertex_attribi4i" => (g.glVertexAttribI4i)(u(0), a(1), a(2), a(3), a(4)),
531            "vertex_attribi4ui" => (g.glVertexAttribI4ui)(u(0), u(1), u(2), u(3), u(4)),
532            "get_vertex_attribiv" => {
533                let v = be.int(|p| (g.glGetVertexAttribiv)(u(0), u(1), p));
534                ret(if u(1) == 0x889F { be.model_name(Buffer, v as u32) as i64 } else { v as i64 })
535            }
536            "get_vertex_attribfv" => {
537                let mut v = [0f32; 4];
538                (g.glGetVertexAttribfv)(u(0), u(1), v.as_mut_ptr() as *mut c_void);
539                let n = if u(1) == 0x8626 { 4 } else { 1 };
540                ret(put_f(mem, u(2), u(3), &v[..n])? as i64)
541            }
542            "get_vertex_attrib_offset" => {
543                let mut p: *mut c_void = std::ptr::null_mut();
544                (g.glGetVertexAttribPointerv)(u(0), u(1), &mut p as *mut *mut c_void as *mut c_void);
545                ret(p as usize as i64)
546            }
547
548            // ---- drawing ----
549            "draw_arrays" => (g.glDrawArrays)(u(0), a(1), a(2)),
550            "draw_elements" => (g.glDrawElements)(u(0), a(1), u(2), off(u(3))),
551            "draw_arrays_instanced" => (g.glDrawArraysInstanced)(u(0), a(1), a(2), a(3)),
552            "draw_elements_instanced" => (g.glDrawElementsInstanced)(u(0), a(1), u(2), off(u(3)), a(4)),
553            "draw_range_elements" => (g.glDrawRangeElements)(u(0), u(1), u(2), a(3), u(4), off(u(5))),
554            "draw_buffers" => (g.glDrawBuffers)(a(1), at(u(0))),
555            "clear_bufferiv" => (g.glClearBufferiv)(u(0), a(1), at(u(2))),
556            "clear_bufferuiv" => (g.glClearBufferuiv)(u(0), a(1), at(u(2))),
557            "clear_bufferfv" => (g.glClearBufferfv)(u(0), a(1), at(u(2))),
558            "clear_bufferfi" => (g.glClearBufferfi)(u(0), a(1), f(2), a(3)),
559
560            // ---- textures ----
561            "bind_texture" => (g.glBindTexture)(u(0), be.obj(Texture, u(1))),
562            "tex_parameteri" => (g.glTexParameteri)(u(0), u(1), a(2)),
563            "tex_parameterf" => (g.glTexParameterf)(u(0), u(1), f(2)),
564            "get_tex_parameteriv" => ret(be.int(|p| (g.glGetTexParameteriv)(u(0), u(1), p)) as i64),
565            "get_tex_parameterfv" | "get_sampler_parameterfv" => {
566                let mut v = 0f32;
567                if name == "get_tex_parameterfv" {
568                    (g.glGetTexParameterfv)(u(0), u(1), &mut v as *mut f32 as *mut c_void);
569                } else {
570                    (g.glGetSamplerParameterfv)(be.obj(Sampler, u(0)), u(1), &mut v as *mut f32 as *mut c_void);
571                }
572                if let Some(r) = results.first_mut() {
573                    *r = Val::F32(v.to_bits());
574                }
575            }
576            "tex_image_2d" | "tex_image_3d" | "tex_sub_image_2d" | "tex_sub_image_3d" => {
577                // pixels: none, guest memory, or (an unpack buffer bound) an offset into it
578                let n = args.len();
579                let (ptr, len) = (u(n - 2), u(n - 1));
580                let px = if model.bound_buffer(PIXEL_UNPACK_BUFFER) != 0 { off(len) } else { at(ptr) };
581                match name {
582                    "tex_image_2d" => (g.glTexImage2D)(u(0), a(1), a(2), a(3), a(4), a(5), u(6), u(7), px),
583                    "tex_image_3d" => (g.glTexImage3D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), u(7), u(8), px),
584                    "tex_sub_image_2d" => (g.glTexSubImage2D)(u(0), a(1), a(2), a(3), a(4), a(5), u(6), u(7), px),
585                    _ => (g.glTexSubImage3D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7), u(8), u(9), px),
586                }
587            }
588            "tex_storage_2d" => (g.glTexStorage2D)(u(0), a(1), u(2), a(3), a(4)),
589            "tex_storage_3d" => (g.glTexStorage3D)(u(0), a(1), u(2), a(3), a(4), a(5)),
590            "compressed_tex_image_2d" => (g.glCompressedTexImage2D)(u(0), a(1), u(2), a(3), a(4), a(5), a(7), at(u(6))),
591            "compressed_tex_image_3d" => (g.glCompressedTexImage3D)(u(0), a(1), u(2), a(3), a(4), a(5), a(6), a(8), at(u(7))),
592            "compressed_tex_sub_image_2d" => (g.glCompressedTexSubImage2D)(u(0), a(1), a(2), a(3), a(4), a(5), u(6), a(8), at(u(7))),
593            "compressed_tex_sub_image_3d" => {
594                (g.glCompressedTexSubImage3D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7), u(8), a(10), at(u(9)))
595            }
596            "copy_tex_image_2d" => (g.glCopyTexImage2D)(u(0), a(1), u(2), a(3), a(4), a(5), a(6), a(7)),
597            "copy_tex_sub_image_2d" => (g.glCopyTexSubImage2D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7)),
598            "copy_tex_sub_image_3d" => (g.glCopyTexSubImage3D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7), a(8)),
599            "generate_mipmap" => (g.glGenerateMipmap)(u(0)),
600
601            // ---- samplers ----
602            "bind_sampler" => (g.glBindSampler)(u(0), be.obj(Sampler, u(1))),
603            "sampler_parameteri" => (g.glSamplerParameteri)(be.obj(Sampler, u(0)), u(1), a(2)),
604            "sampler_parameterf" => (g.glSamplerParameterf)(be.obj(Sampler, u(0)), u(1), f(2)),
605            "get_sampler_parameteriv" => ret(be.int(|p| (g.glGetSamplerParameteriv)(be.obj(Sampler, u(0)), u(1), p)) as i64),
606
607            // ---- framebuffers, renderbuffers ----
608            "bind_framebuffer" => (g.glBindFramebuffer)(u(0), be.obj(Framebuffer, u(1))),
609            "check_framebuffer_status" => ret((g.glCheckFramebufferStatus)(u(0)) as i64),
610            "framebuffer_texture_2d" => (g.glFramebufferTexture2D)(u(0), u(1), u(2), be.obj(Texture, u(3)), a(4)),
611            "framebuffer_texture_layer" => (g.glFramebufferTextureLayer)(u(0), u(1), be.obj(Texture, u(2)), a(3), a(4)),
612            "framebuffer_renderbuffer" => (g.glFramebufferRenderbuffer)(u(0), u(1), u(2), be.obj(Renderbuffer, u(3))),
613            "get_framebuffer_attachment_parameteriv" => {
614                let (t, att, p) = (u(0), u(1), u(2));
615                let v = be.int(|q| (g.glGetFramebufferAttachmentParameteriv)(t, att, p, q));
616                if p == 0x8CD1 {
617                    // OBJECT_NAME: the guest's name of the texture or renderbuffer
618                    let ty = be.int(|q| (g.glGetFramebufferAttachmentParameteriv)(t, att, 0x8CD0, q)) as u32;
619                    let kind = if ty == 0x1702 { Texture } else { Renderbuffer };
620                    ret(be.model_name(kind, v as u32) as i64)
621                } else {
622                    ret(v as i64)
623                }
624            }
625            "blit_framebuffer" => (g.glBlitFramebuffer)(a(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7), u(8), u(9)),
626            "invalidate_framebuffer" => (g.glInvalidateFramebuffer)(u(0), a(2), at(u(1))),
627            "invalidate_sub_framebuffer" => (g.glInvalidateSubFramebuffer)(u(0), a(2), at(u(1)), a(3), a(4), a(5), a(6)),
628            "read_buffer" => (g.glReadBuffer)(u(0)),
629            "read_pixels" => {
630                let dst = if model.bound_buffer(PIXEL_PACK_BUFFER) != 0 { off(u(6)) as *mut c_void } else { at_mut(u(6)) };
631                (g.glReadPixels)(a(0), a(1), a(2), a(3), u(4), u(5), dst)
632            }
633            "bind_renderbuffer" => (g.glBindRenderbuffer)(u(0), be.obj(Renderbuffer, u(1))),
634            "renderbuffer_storage" => (g.glRenderbufferStorage)(u(0), u(1), a(2), a(3)),
635            "renderbuffer_storage_multisample" => (g.glRenderbufferStorageMultisample)(u(0), a(1), u(2), a(3), a(4)),
636            "get_renderbuffer_parameteriv" => ret(be.int(|p| (g.glGetRenderbufferParameteriv)(u(0), u(1), p)) as i64),
637
638            // ---- shaders and programs ----
639            "shader_source" => {
640                let p = at(u(1));
641                let len = a(2);
642                (g.glShaderSource)(be.obj(Shader, u(0)), 1, &p as *const *const c_void as *const c_void, &len as *const i32 as *const c_void)
643            }
644            "compile_shader" => (g.glCompileShader)(be.obj(Shader, u(0))),
645            "get_shaderiv" => {
646                if u(1) != 0x8B4F && u(1) != 0x8B88 {
647                    ret(be.int(|p| (g.glGetShaderiv)(be.obj(Shader, u(0)), u(1), p)) as i64)
648                }
649            }
650            "get_shader_info_log" => {
651                let log = be.info_log(true, be.obj(Shader, u(0)));
652                ret(text(mem, u(1), u(2), &log)? as i64)
653            }
654            "attach_shader" => (g.glAttachShader)(be.obj(Program, u(0)), be.obj(Shader, u(1))),
655            "detach_shader" => (g.glDetachShader)(be.obj(Program, u(0)), be.obj(Shader, u(1))),
656            "link_program" => {
657                be.uniforms.retain(|(p, _), _| *p != u(0));
658                (g.glLinkProgram)(be.obj(Program, u(0)))
659            }
660            "use_program" => (g.glUseProgram)(be.obj(Program, u(0))),
661            "validate_program" => (g.glValidateProgram)(be.obj(Program, u(0))),
662            "get_programiv" => ret(be.int(|p| (g.glGetProgramiv)(be.obj(Program, u(0)), u(1), p)) as i64),
663            "get_program_info_log" => {
664                let log = be.info_log(false, be.obj(Program, u(0)));
665                ret(text(mem, u(1), u(2), &log)? as i64)
666            }
667            "get_attached_shaders" => {
668                let mut v = [0u32; 16];
669                let mut n = 0i32;
670                (g.glGetAttachedShaders)(be.obj(Program, u(0)), 16, &mut n as *mut i32 as *mut c_void, v.as_mut_ptr() as *mut c_void);
671                let names: Vec<i64> = v[..n.max(0) as usize].iter().map(|&s| be.model_name(Shader, s) as i64).collect();
672                ret(put_i(mem, u(1), u(2), &names, false)? as i64)
673            }
674            "bind_attrib_location" => {
675                let n = c_string(&guest_str(mem, u(2), u(3))?);
676                (g.glBindAttribLocation)(be.obj(Program, u(0)), u(1), n.as_ptr() as *const c_void)
677            }
678            "get_attrib_location" | "get_frag_data_location" => {
679                let n = c_string(&guest_str(mem, u(1), u(2))?);
680                let p = be.obj(Program, u(0));
681                ret(if name == "get_attrib_location" {
682                    (g.glGetAttribLocation)(p, n.as_ptr() as *const c_void)
683                } else {
684                    (g.glGetFragDataLocation)(p, n.as_ptr() as *const c_void)
685                } as i64)
686            }
687            "get_active_attrib" | "get_active_uniform" | "get_transform_feedback_varying" => {
688                let p = be.obj(Program, u(0));
689                let (count_p, max_p, get): (u32, u32, unsafe extern "system" fn(u32, u32, i32, *mut c_void, *mut c_void, *mut c_void, *mut c_void)) =
690                    match name {
691                        "get_active_attrib" => (0x8B89, 0x8B8A, g.glGetActiveAttrib),
692                        "get_active_uniform" => (0x8B86, 0x8B87, g.glGetActiveUniform),
693                        _ => (0x8C83, 0x8C76, g.glGetTransformFeedbackVarying),
694                    };
695                let count = be.int(|q| (g.glGetProgramiv)(p, count_p, q));
696                if u(1) as i64 >= count as i64 {
697                    model.error(INVALID_VALUE);
698                    {
699                    ret(-1);
700                    return Ok(());
701                }
702                }
703                let max = be.int(|q| (g.glGetProgramiv)(p, max_p, q)).max(1);
704                let mut buf = vec![0u8; max as usize];
705                let (mut len, mut size, mut ty) = (0i32, 0i32, 0u32);
706                get(
707                    p, u(1), max, &mut len as *mut i32 as *mut c_void, &mut size as *mut i32 as *mut c_void,
708                    &mut ty as *mut u32 as *mut c_void, buf.as_mut_ptr() as *mut c_void,
709                );
710                let s = String::from_utf8_lossy(&buf[..len.max(0) as usize]).into_owned();
711                put_i(mem, u(4), 2, &[size as i64, ty as i64], false)?;
712                ret(text(mem, u(2), u(3), &s)? as i64)
713            }
714            "get_uniform_location" => {
715                let uname = guest_str(mem, u(1), u(2))?;
716                let p = be.obj(Program, u(0));
717                let c = c_string(&uname);
718                let loc = (g.glGetUniformLocation)(p, c.as_ptr() as *const c_void);
719                let model_loc = model_result as i32;
720                if loc < 0 {
721                    // not in the linked program: -1, and the name gets no location (as in browsers)
722                    if let Some(t) = model.uniforms.get_mut(&u(0)) {
723                        if t.by_name.get(&uname) == Some(&model_loc) && model_loc == t.count - 1 {
724                            t.by_name.remove(&uname);
725                            t.count -= 1;
726                        }
727                    }
728                    {
729                    ret(-1);
730                    return Ok(());
731                }
732                }
733                if !be.uniforms.contains_key(&(u(0), model_loc)) {
734                    let comps = be.uniform_components(p, &uname);
735                    be.uniforms.insert((u(0), model_loc), (loc, comps));
736                }
737            }
738            "get_uniform_index" => {
739                let c = c_string(&guest_str(mem, u(1), u(2))?);
740                let ptr = c.as_ptr();
741                let mut idx = 0u32;
742                (g.glGetUniformIndices)(be.obj(Program, u(0)), 1, &ptr as *const *const c_char as *const c_void, &mut idx as *mut u32 as *mut c_void);
743                ret(idx as i32 as i64)
744            }
745            "get_active_uniformsiv" => {
746                (g.glGetActiveUniformsiv)(be.obj(Program, u(0)), a(2), at(u(1)), u(3), at_mut(u(4)))
747            }
748            "get_uniform_block_index" => {
749                let c = c_string(&guest_str(mem, u(1), u(2))?);
750                ret((g.glGetUniformBlockIndex)(be.obj(Program, u(0)), c.as_ptr() as *const c_void) as i32 as i64)
751            }
752            "get_active_uniform_block_name" => {
753                let p = be.obj(Program, u(0));
754                let n = be.int(|q| (g.glGetActiveUniformBlockiv)(p, u(1), 0x8A41, q)).max(1);
755                let mut buf = vec![0u8; n as usize];
756                let mut len = 0i32;
757                (g.glGetActiveUniformBlockName)(p, u(1), n, &mut len as *mut i32 as *mut c_void, buf.as_mut_ptr() as *mut c_void);
758                let s = String::from_utf8_lossy(&buf[..len.max(0) as usize]).into_owned();
759                ret(text(mem, u(2), u(3), &s)? as i64)
760            }
761            "get_active_uniform_blockiv" => {
762                let p = be.obj(Program, u(0));
763                let n = if u(2) == 0x8A43 { be.int(|q| (g.glGetActiveUniformBlockiv)(p, u(1), 0x8A42, q)).max(0) as usize } else { 1 };
764                let mut v = vec![0i32; n.max(1)];
765                (g.glGetActiveUniformBlockiv)(p, u(1), u(2), v.as_mut_ptr() as *mut c_void);
766                v.truncate(n);
767                let v: Vec<i64> = v.into_iter().map(i64::from).collect();
768                ret(put_i(mem, u(3), u(4), &v, false)? as i64)
769            }
770            "uniform_block_binding" => (g.glUniformBlockBinding)(be.obj(Program, u(0)), u(1), u(2)),
771            "get_uniformfv" | "get_uniformiv" | "get_uniformuiv" => {
772                let Some(&(loc, comps)) = be.uniforms.get(&(u(0), a(1))) else { return Ok(()) };
773                let p = be.obj(Program, u(0));
774                let n = comps.min(16) as usize;
775                if name == "get_uniformfv" {
776                    let mut v = [0f32; 16];
777                    (g.glGetUniformfv)(p, loc, v.as_mut_ptr() as *mut c_void);
778                    ret(put_f(mem, u(2), u(3), &v[..n])? as i64)
779                } else {
780                    let mut v = [0i32; 16];
781                    if name == "get_uniformiv" {
782                        (g.glGetUniformiv)(p, loc, v.as_mut_ptr() as *mut c_void);
783                    } else {
784                        (g.glGetUniformuiv)(p, loc, v.as_mut_ptr() as *mut c_void);
785                    }
786                    let v: Vec<i64> = v[..n].iter().map(|&x| x as i64).collect();
787                    ret(put_i(mem, u(2), u(3), &v, false)? as i64)
788                }
789            }
790            "transform_feedback_varyings" => {
791                let bytes = guest_slice(mem, u(1), u(2) as u64)?;
792                let names: Vec<std::ffi::CString> = bytes
793                    .split(|&b| b == 0)
794                    .take(u(3) as usize)
795                    .map(|s| c_string(&String::from_utf8_lossy(s)))
796                    .collect();
797                let ptrs: Vec<*const c_char> = names.iter().map(|c| c.as_ptr()).collect();
798                (g.glTransformFeedbackVaryings)(be.obj(Program, u(0)), ptrs.len() as i32, ptrs.as_ptr() as *const c_void, u(4))
799            }
800
801            // ---- uniforms (the model checked the location) ----
802            n if n.starts_with("uniform") => {
803                let Some(&(loc, _)) = be.uniforms.get(&(model.program, a(0))) else { return Ok(()) };
804                match n {
805                    "uniform1f" => (g.glUniform1f)(loc, f(1)),
806                    "uniform2f" => (g.glUniform2f)(loc, f(1), f(2)),
807                    "uniform3f" => (g.glUniform3f)(loc, f(1), f(2), f(3)),
808                    "uniform4f" => (g.glUniform4f)(loc, f(1), f(2), f(3), f(4)),
809                    "uniform1i" => (g.glUniform1i)(loc, a(1)),
810                    "uniform2i" => (g.glUniform2i)(loc, a(1), a(2)),
811                    "uniform3i" => (g.glUniform3i)(loc, a(1), a(2), a(3)),
812                    "uniform4i" => (g.glUniform4i)(loc, a(1), a(2), a(3), a(4)),
813                    "uniform1ui" => (g.glUniform1ui)(loc, u(1)),
814                    "uniform2ui" => (g.glUniform2ui)(loc, u(1), u(2)),
815                    "uniform3ui" => (g.glUniform3ui)(loc, u(1), u(2), u(3)),
816                    "uniform4ui" => (g.glUniform4ui)(loc, u(1), u(2), u(3), u(4)),
817                    "uniform1fv" => (g.glUniform1fv)(loc, a(1), at(u(2))),
818                    "uniform2fv" => (g.glUniform2fv)(loc, a(1), at(u(2))),
819                    "uniform3fv" => (g.glUniform3fv)(loc, a(1), at(u(2))),
820                    "uniform4fv" => (g.glUniform4fv)(loc, a(1), at(u(2))),
821                    "uniform1iv" => (g.glUniform1iv)(loc, a(1), at(u(2))),
822                    "uniform2iv" => (g.glUniform2iv)(loc, a(1), at(u(2))),
823                    "uniform3iv" => (g.glUniform3iv)(loc, a(1), at(u(2))),
824                    "uniform4iv" => (g.glUniform4iv)(loc, a(1), at(u(2))),
825                    "uniform1uiv" => (g.glUniform1uiv)(loc, a(1), at(u(2))),
826                    "uniform2uiv" => (g.glUniform2uiv)(loc, a(1), at(u(2))),
827                    "uniform3uiv" => (g.glUniform3uiv)(loc, a(1), at(u(2))),
828                    "uniform4uiv" => (g.glUniform4uiv)(loc, a(1), at(u(2))),
829                    _ => {
830                        let m = match n {
831                            "uniform_matrix2fv" => g.glUniformMatrix2fv,
832                            "uniform_matrix3fv" => g.glUniformMatrix3fv,
833                            "uniform_matrix4fv" => g.glUniformMatrix4fv,
834                            "uniform_matrix2x3fv" => g.glUniformMatrix2x3fv,
835                            "uniform_matrix3x2fv" => g.glUniformMatrix3x2fv,
836                            "uniform_matrix2x4fv" => g.glUniformMatrix2x4fv,
837                            "uniform_matrix4x2fv" => g.glUniformMatrix4x2fv,
838                            "uniform_matrix3x4fv" => g.glUniformMatrix3x4fv,
839                            _ => g.glUniformMatrix4x3fv,
840                        };
841                        m(loc, a(1), (u(2) != 0) as u8, at(u(3)))
842                    }
843                }
844            }
845
846            // ---- queries, transform feedback ----
847            "begin_query" => (g.glBeginQuery)(u(0), be.obj(Query, u(1))),
848            "end_query" => (g.glEndQuery)(u(0)),
849            "get_query_objectuiv" => {
850                // the model says when it's ready (the next frame); GL has the value
851                if u(1) == 0x8866 && model_result != 0 {
852                    let mut v = 0u32;
853                    (g.glGetQueryObjectuiv)(be.obj(Query, u(0)), 0x8866, &mut v as *mut u32 as *mut c_void);
854                    ret(v as i64)
855                }
856            }
857            "bind_transform_feedback" => (g.glBindTransformFeedback)(u(0), be.obj(TransformFeedback, u(1))),
858            "begin_transform_feedback" => (g.glBeginTransformFeedback)(u(0)),
859            "end_transform_feedback" => (g.glEndTransformFeedback)(),
860            "pause_transform_feedback" => (g.glPauseTransformFeedback)(),
861            "resume_transform_feedback" => (g.glResumeTransformFeedback)(),
862            _ => wasmtime::bail!("gasm:gl.{name}: no GL backend for it"),
863        }
864    }
865    Ok(())
866}