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    /// The default framebuffer as RGBA8, top to bottom (headless screenshots).
243    pub fn read_frame(&self) -> Option<(u32, u32, Vec<u8>)> {
244        self.backend.as_ref().map(|b| b.angle.read_frame())
245    }
246    /// The drawable size, if a backend draws.
247    pub fn size(&self) -> Option<(u32, u32)> {
248        self.backend.as_ref().map(|b| b.angle.size())
249    }
250    pub fn renderer(&self) -> Option<&str> {
251        self.backend.as_ref().map(|b| b.angle.renderer.as_str())
252    }
253}
254
255/// Execute one call the model passed. `results` holds the model's answer, which
256/// stays unless GL has the real one.
257pub(super) fn forward(
258    be: &mut Backend,
259    model: &mut Gl,
260    name: &str,
261    args: &[Val],
262    results: &mut [Val],
263    mem: &mut [u8],
264) -> wasmtime::Result<()> {
265    let a = |i: usize| -> i32 {
266        match args[i] {
267            Val::I32(v) => v,
268            Val::I64(v) => v as i32,
269            _ => 0,
270        }
271    };
272    let u = |i: usize| a(i) as u32;
273    let f = |i: usize| match args[i] {
274        Val::F32(b) => f32::from_bits(b),
275        _ => 0.0,
276    };
277    let model_result = match results.first() {
278        Some(Val::I32(v)) => *v as i64,
279        _ => 0,
280    };
281    let mut ret = |v: i64| {
282        if let Some(r) = results.first_mut() {
283            *r = Val::I32(v as i32);
284        }
285    };
286    let base = mem.as_mut_ptr();
287    let at = |p: u32| -> *const c_void { if p == 0 { std::ptr::null() } else { unsafe { base.add(p as usize) as *const c_void } } };
288    let at_mut = |p: u32| -> *mut c_void { unsafe { base.add(p as usize) as *mut c_void } };
289    let off = |o: u32| o as usize as *const c_void;
290    let g = &be.angle.gl as *const crate::gles::Gles;
291    let g = unsafe { &*g };
292    use Kind::*;
293    unsafe {
294        match name {
295            // ---- frames, context ----
296            "width" => ret(be.angle.size().0 as i64),
297            "height" => ret(be.angle.size().1 as i64),
298            "present" => {
299                be.angle.swap();
300                be.presented = true;
301            }
302            "get_error" => {
303                if model_result == 0 {
304                    ret((g.glGetError)() as i64)
305                }
306            }
307            "get_string" => {
308                let s = if u(0) == GL_EXTENSIONS { be.webgl_extensions().join(" ") } else { cstr((g.glGetString)(u(0))) };
309                ret(text(mem, u(1), u(2), &s)? as i64)
310            }
311            "enable_extension" => {
312                let n = guest_str(mem, u(0), u(1))?;
313                ret(be.enable_extension(&n) as i64)
314            }
315            "get_integerv" | "get_floatv" | "get_integer64v" => {
316                let pname = u(0);
317                if model.model_param(pname).is_some() {
318                    return Ok(());
319                }
320                be.drain(model);
321                let n = param_count(g, pname).max(1);
322                let (dst, count) = (u(1), u(2));
323                match name {
324                    "get_floatv" => {
325                        let mut v = vec![0f32; n.max(16)];
326                        (g.glGetFloatv)(pname, v.as_mut_ptr() as *mut c_void);
327                        if (g.glGetError)() == INVALID_ENUM {
328                            model.error(INVALID_ENUM);
329                            {
330                    ret(-1);
331                    return Ok(());
332                }
333                        }
334                        v.truncate(n);
335                        ret(put_f(mem, dst, count, &v)? as i64)
336                    }
337                    _ => {
338                        let mut v = vec![0i64; n.max(16)];
339                        (g.glGetInteger64v)(pname, v.as_mut_ptr() as *mut c_void);
340                        if (g.glGetError)() == INVALID_ENUM {
341                            model.error(INVALID_ENUM);
342                            {
343                    ret(-1);
344                    return Ok(());
345                }
346                        }
347                        v.truncate(n);
348                        // objects come back as the guest's names
349                        match pname {
350                            0x8919 => v[0] = be.model_name(Sampler, v[0] as u32) as i64,
351                            0x8E25 => v[0] = be.model_name(TransformFeedback, v[0] as u32) as i64,
352                            _ => {}
353                        }
354                        ret(put_i(mem, dst, count, &v, name == "get_integer64v")? as i64)
355                    }
356                }
357            }
358            "get_integeri_v" => {
359                be.drain(model);
360                let mut v = 0i64;
361                (g.glGetInteger64i_v)(u(0), u(1), &mut v as *mut i64 as *mut c_void);
362                if (g.glGetError)() != 0 {
363                    model.error(INVALID_ENUM);
364                    {
365                    ret(-1);
366                    return Ok(());
367                }
368                }
369                if u(0) == 0x8A28 || u(0) == 0x8C8F {
370                    v = be.model_name(Buffer, v as u32) as i64;
371                }
372                ret(put_i(mem, u(2), u(3), &[v], false)? as i64)
373            }
374            "get_internalformativ" => {
375                let (t, fmt, pname) = (u(0), u(1), u(2));
376                let n = if pname == 0x80A9 { be.int(|p| (g.glGetInternalformativ)(t, fmt, 0x9380, 1, p)).max(0) as usize } else { 1 };
377                let mut v = vec![0i32; n.max(1)];
378                (g.glGetInternalformativ)(t, fmt, pname, n as i32, v.as_mut_ptr() as *mut c_void);
379                v.truncate(n);
380                let v: Vec<i64> = v.into_iter().map(i64::from).collect();
381                ret(put_i(mem, u(3), u(4), &v, false)? as i64)
382            }
383            "get_shader_precision_format" => {
384                let (mut range, mut precision) = ([0i32; 2], 0i32);
385                (g.glGetShaderPrecisionFormat)(u(0), u(1), range.as_mut_ptr() as *mut c_void, &mut precision as *mut i32 as *mut c_void);
386                put_i(mem, u(2), 3, &[range[0] as i64, range[1] as i64, precision as i64], false)?;
387            }
388
389            // ---- state ----
390            "active_texture" => (g.glActiveTexture)(u(0)),
391            "blend_color" => (g.glBlendColor)(f(0), f(1), f(2), f(3)),
392            "blend_equation" => (g.glBlendEquation)(u(0)),
393            "blend_equation_separate" => (g.glBlendEquationSeparate)(u(0), u(1)),
394            "blend_func" => (g.glBlendFunc)(u(0), u(1)),
395            "blend_func_separate" => (g.glBlendFuncSeparate)(u(0), u(1), u(2), u(3)),
396            "clear" => (g.glClear)(u(0)),
397            "clear_color" => (g.glClearColor)(f(0), f(1), f(2), f(3)),
398            "clear_depthf" => (g.glClearDepthf)(f(0)),
399            "clear_stencil" => (g.glClearStencil)(a(0)),
400            "color_mask" => (g.glColorMask)((u(0) != 0) as u8, (u(1) != 0) as u8, (u(2) != 0) as u8, (u(3) != 0) as u8),
401            "cull_face" => (g.glCullFace)(u(0)),
402            "depth_func" => (g.glDepthFunc)(u(0)),
403            "depth_mask" => (g.glDepthMask)((u(0) != 0) as u8),
404            "depth_rangef" => (g.glDepthRangef)(f(0), f(1)),
405            "disable" => (g.glDisable)(u(0)),
406            "enable" => (g.glEnable)(u(0)),
407            "is_enabled" => ret((g.glIsEnabled)(u(0)) as i64),
408            "front_face" => (g.glFrontFace)(u(0)),
409            "hint" => (g.glHint)(u(0), u(1)),
410            "line_width" => (g.glLineWidth)(f(0)),
411            "pixel_storei" => {
412                // WebGL-only pnames (UNPACK_FLIP_Y_WEBGL, ...) have no GLES equivalent
413                if !(0x9240..=0x9244).contains(&u(0)) {
414                    (g.glPixelStorei)(u(0), a(1))
415                }
416            }
417            "polygon_offset" => (g.glPolygonOffset)(f(0), f(1)),
418            "sample_coverage" => (g.glSampleCoverage)(f(0), (u(1) != 0) as u8),
419            "scissor" => (g.glScissor)(a(0), a(1), a(2), a(3)),
420            "viewport" => (g.glViewport)(a(0), a(1), a(2), a(3)),
421            "stencil_func" => (g.glStencilFunc)(u(0), a(1), u(2)),
422            "stencil_func_separate" => (g.glStencilFuncSeparate)(u(0), u(1), a(2), u(3)),
423            "stencil_mask" => (g.glStencilMask)(u(0)),
424            "stencil_mask_separate" => (g.glStencilMaskSeparate)(u(0), u(1)),
425            "stencil_op" => (g.glStencilOp)(u(0), u(1), u(2)),
426            "stencil_op_separate" => (g.glStencilOpSeparate)(u(0), u(1), u(2), u(3)),
427            "finish" => (g.glFinish)(),
428            "flush" => (g.glFlush)(),
429
430            // ---- object lifetimes (the model made or retired the name) ----
431            "create_buffer" | "create_texture" | "create_vertex_array" | "create_sampler" | "create_framebuffer"
432            | "create_renderbuffer" | "create_query" | "create_transform_feedback" => {
433                let (kind, make): (Kind, unsafe extern "system" fn(i32, *mut c_void)) = match name {
434                    "create_buffer" => (Buffer, g.glGenBuffers),
435                    "create_texture" => (Texture, g.glGenTextures),
436                    "create_vertex_array" => (VertexArray, g.glGenVertexArrays),
437                    "create_sampler" => (Sampler, g.glGenSamplers),
438                    "create_framebuffer" => (Framebuffer, g.glGenFramebuffers),
439                    "create_renderbuffer" => (Renderbuffer, g.glGenRenderbuffers),
440                    "create_query" => (Query, g.glGenQueries),
441                    _ => (TransformFeedback, g.glGenTransformFeedbacks),
442                };
443                let mut x = 0u32;
444                make(1, &mut x as *mut u32 as *mut c_void);
445                be.put_name(kind, model_result as u32, x);
446            }
447            "delete_buffer" | "delete_texture" | "delete_vertex_array" | "delete_sampler" | "delete_framebuffer"
448            | "delete_renderbuffer" | "delete_query" | "delete_transform_feedback" => {
449                let (kind, del): (Kind, unsafe extern "system" fn(i32, *const c_void)) = match name {
450                    "delete_buffer" => (Buffer, g.glDeleteBuffers),
451                    "delete_texture" => (Texture, g.glDeleteTextures),
452                    "delete_vertex_array" => (VertexArray, g.glDeleteVertexArrays),
453                    "delete_sampler" => (Sampler, g.glDeleteSamplers),
454                    "delete_framebuffer" => (Framebuffer, g.glDeleteFramebuffers),
455                    "delete_renderbuffer" => (Renderbuffer, g.glDeleteRenderbuffers),
456                    "delete_query" => (Query, g.glDeleteQueries),
457                    _ => (TransformFeedback, g.glDeleteTransformFeedbacks),
458                };
459                if let Some(x) = be.take(kind, u(0)) {
460                    del(1, &x as *const u32 as *const c_void);
461                }
462            }
463            "create_shader" => {
464                let x = (g.glCreateShader)(u(0));
465                be.put_name(Shader, model_result as u32, x);
466            }
467            "create_program" => {
468                let x = (g.glCreateProgram)();
469                be.put_name(Program, model_result as u32, x);
470            }
471            "delete_shader" => {
472                if let Some(x) = be.take(Shader, u(0)) {
473                    (g.glDeleteShader)(x)
474                }
475            }
476            "delete_program" => {
477                be.uniforms.retain(|(p, _), _| *p != u(0));
478                if let Some(x) = be.take(Program, u(0)) {
479                    (g.glDeleteProgram)(x)
480                }
481            }
482            "fence_sync" => {
483                let s = (g.glFenceSync)(u(0), u(1));
484                be.syncs.insert(model_result as u32, s as usize);
485            }
486            "delete_sync" => {
487                if let Some(s) = be.syncs.remove(&u(0)) {
488                    (g.glDeleteSync)(s as *const c_void)
489                }
490            }
491            // the model answers these (names, readiness)
492            "is_buffer" | "is_texture" | "is_vertex_array" | "is_sampler" | "is_framebuffer" | "is_renderbuffer"
493            | "is_shader" | "is_program" | "is_query" | "is_sync" | "is_transform_feedback" | "frame_shown"
494            | "get_shader_source" | "get_queryiv" | "client_wait_sync" | "wait_sync" | "get_synciv" => {}
495
496            // ---- buffers ----
497            "bind_buffer" => (g.glBindBuffer)(u(0), be.obj(Buffer, u(1))),
498            "bind_buffer_base" => (g.glBindBufferBase)(u(0), u(1), be.obj(Buffer, u(2))),
499            "bind_buffer_range" => (g.glBindBufferRange)(u(0), u(1), be.obj(Buffer, u(2)), u(3) as isize, u(4) as isize),
500            "buffer_data" => (g.glBufferData)(u(0), u(2) as isize, at(u(1)), u(3)),
501            "buffer_sub_data" => (g.glBufferSubData)(u(0), u(1) as isize, u(3) as isize, at(u(2))),
502            "copy_buffer_sub_data" => (g.glCopyBufferSubData)(u(0), u(1), u(2) as isize, u(3) as isize, u(4) as isize),
503            "get_buffer_sub_data" => {
504                // GLES reads buffers back through a mapping
505                let (t, o, dst, len) = (u(0), u(1), u(2), u(3));
506                if len > 0 {
507                    let p = (g.glMapBufferRange)(t, o as isize, len as isize, 0x0001);
508                    if !p.is_null() {
509                        let out = guest_slice_mut(mem, dst, len as u64)?;
510                        std::ptr::copy_nonoverlapping(p as *const u8, out.as_mut_ptr(), len as usize);
511                        (g.glUnmapBuffer)(t);
512                    }
513                }
514            }
515            "get_buffer_parameteriv" => ret(be.int(|p| (g.glGetBufferParameteriv)(u(0), u(1), p)) as i64),
516
517            // ---- vertex arrays ----
518            "bind_vertex_array" => (g.glBindVertexArray)(be.obj(VertexArray, u(0))),
519            "enable_vertex_attrib_array" => (g.glEnableVertexAttribArray)(u(0)),
520            "disable_vertex_attrib_array" => (g.glDisableVertexAttribArray)(u(0)),
521            "vertex_attrib_pointer" => (g.glVertexAttribPointer)(u(0), a(1), u(2), (u(3) != 0) as u8, a(4), off(u(5))),
522            "vertex_attrib_ipointer" => (g.glVertexAttribIPointer)(u(0), a(1), u(2), a(3), off(u(4))),
523            "vertex_attrib_divisor" => (g.glVertexAttribDivisor)(u(0), u(1)),
524            "vertex_attrib4f" => (g.glVertexAttrib4f)(u(0), f(1), f(2), f(3), f(4)),
525            "vertex_attribi4i" => (g.glVertexAttribI4i)(u(0), a(1), a(2), a(3), a(4)),
526            "vertex_attribi4ui" => (g.glVertexAttribI4ui)(u(0), u(1), u(2), u(3), u(4)),
527            "get_vertex_attribiv" => {
528                let v = be.int(|p| (g.glGetVertexAttribiv)(u(0), u(1), p));
529                ret(if u(1) == 0x889F { be.model_name(Buffer, v as u32) as i64 } else { v as i64 })
530            }
531            "get_vertex_attribfv" => {
532                let mut v = [0f32; 4];
533                (g.glGetVertexAttribfv)(u(0), u(1), v.as_mut_ptr() as *mut c_void);
534                let n = if u(1) == 0x8626 { 4 } else { 1 };
535                ret(put_f(mem, u(2), u(3), &v[..n])? as i64)
536            }
537            "get_vertex_attrib_offset" => {
538                let mut p: *mut c_void = std::ptr::null_mut();
539                (g.glGetVertexAttribPointerv)(u(0), u(1), &mut p as *mut *mut c_void as *mut c_void);
540                ret(p as usize as i64)
541            }
542
543            // ---- drawing ----
544            "draw_arrays" => (g.glDrawArrays)(u(0), a(1), a(2)),
545            "draw_elements" => (g.glDrawElements)(u(0), a(1), u(2), off(u(3))),
546            "draw_arrays_instanced" => (g.glDrawArraysInstanced)(u(0), a(1), a(2), a(3)),
547            "draw_elements_instanced" => (g.glDrawElementsInstanced)(u(0), a(1), u(2), off(u(3)), a(4)),
548            "draw_range_elements" => (g.glDrawRangeElements)(u(0), u(1), u(2), a(3), u(4), off(u(5))),
549            "draw_buffers" => (g.glDrawBuffers)(a(1), at(u(0))),
550            "clear_bufferiv" => (g.glClearBufferiv)(u(0), a(1), at(u(2))),
551            "clear_bufferuiv" => (g.glClearBufferuiv)(u(0), a(1), at(u(2))),
552            "clear_bufferfv" => (g.glClearBufferfv)(u(0), a(1), at(u(2))),
553            "clear_bufferfi" => (g.glClearBufferfi)(u(0), a(1), f(2), a(3)),
554
555            // ---- textures ----
556            "bind_texture" => (g.glBindTexture)(u(0), be.obj(Texture, u(1))),
557            "tex_parameteri" => (g.glTexParameteri)(u(0), u(1), a(2)),
558            "tex_parameterf" => (g.glTexParameterf)(u(0), u(1), f(2)),
559            "get_tex_parameteriv" => ret(be.int(|p| (g.glGetTexParameteriv)(u(0), u(1), p)) as i64),
560            "get_tex_parameterfv" | "get_sampler_parameterfv" => {
561                let mut v = 0f32;
562                if name == "get_tex_parameterfv" {
563                    (g.glGetTexParameterfv)(u(0), u(1), &mut v as *mut f32 as *mut c_void);
564                } else {
565                    (g.glGetSamplerParameterfv)(be.obj(Sampler, u(0)), u(1), &mut v as *mut f32 as *mut c_void);
566                }
567                if let Some(r) = results.first_mut() {
568                    *r = Val::F32(v.to_bits());
569                }
570            }
571            "tex_image_2d" | "tex_image_3d" | "tex_sub_image_2d" | "tex_sub_image_3d" => {
572                // pixels: none, guest memory, or (an unpack buffer bound) an offset into it
573                let n = args.len();
574                let (ptr, len) = (u(n - 2), u(n - 1));
575                let px = if model.bound_buffer(PIXEL_UNPACK_BUFFER) != 0 { off(len) } else { at(ptr) };
576                match name {
577                    "tex_image_2d" => (g.glTexImage2D)(u(0), a(1), a(2), a(3), a(4), a(5), u(6), u(7), px),
578                    "tex_image_3d" => (g.glTexImage3D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), u(7), u(8), px),
579                    "tex_sub_image_2d" => (g.glTexSubImage2D)(u(0), a(1), a(2), a(3), a(4), a(5), u(6), u(7), px),
580                    _ => (g.glTexSubImage3D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7), u(8), u(9), px),
581                }
582            }
583            "tex_storage_2d" => (g.glTexStorage2D)(u(0), a(1), u(2), a(3), a(4)),
584            "tex_storage_3d" => (g.glTexStorage3D)(u(0), a(1), u(2), a(3), a(4), a(5)),
585            "compressed_tex_image_2d" => (g.glCompressedTexImage2D)(u(0), a(1), u(2), a(3), a(4), a(5), a(7), at(u(6))),
586            "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))),
587            "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))),
588            "compressed_tex_sub_image_3d" => {
589                (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)))
590            }
591            "copy_tex_image_2d" => (g.glCopyTexImage2D)(u(0), a(1), u(2), a(3), a(4), a(5), a(6), a(7)),
592            "copy_tex_sub_image_2d" => (g.glCopyTexSubImage2D)(u(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7)),
593            "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)),
594            "generate_mipmap" => (g.glGenerateMipmap)(u(0)),
595
596            // ---- samplers ----
597            "bind_sampler" => (g.glBindSampler)(u(0), be.obj(Sampler, u(1))),
598            "sampler_parameteri" => (g.glSamplerParameteri)(be.obj(Sampler, u(0)), u(1), a(2)),
599            "sampler_parameterf" => (g.glSamplerParameterf)(be.obj(Sampler, u(0)), u(1), f(2)),
600            "get_sampler_parameteriv" => ret(be.int(|p| (g.glGetSamplerParameteriv)(be.obj(Sampler, u(0)), u(1), p)) as i64),
601
602            // ---- framebuffers, renderbuffers ----
603            "bind_framebuffer" => (g.glBindFramebuffer)(u(0), be.obj(Framebuffer, u(1))),
604            "check_framebuffer_status" => ret((g.glCheckFramebufferStatus)(u(0)) as i64),
605            "framebuffer_texture_2d" => (g.glFramebufferTexture2D)(u(0), u(1), u(2), be.obj(Texture, u(3)), a(4)),
606            "framebuffer_texture_layer" => (g.glFramebufferTextureLayer)(u(0), u(1), be.obj(Texture, u(2)), a(3), a(4)),
607            "framebuffer_renderbuffer" => (g.glFramebufferRenderbuffer)(u(0), u(1), u(2), be.obj(Renderbuffer, u(3))),
608            "get_framebuffer_attachment_parameteriv" => {
609                let (t, att, p) = (u(0), u(1), u(2));
610                let v = be.int(|q| (g.glGetFramebufferAttachmentParameteriv)(t, att, p, q));
611                if p == 0x8CD1 {
612                    // OBJECT_NAME: the guest's name of the texture or renderbuffer
613                    let ty = be.int(|q| (g.glGetFramebufferAttachmentParameteriv)(t, att, 0x8CD0, q)) as u32;
614                    let kind = if ty == 0x1702 { Texture } else { Renderbuffer };
615                    ret(be.model_name(kind, v as u32) as i64)
616                } else {
617                    ret(v as i64)
618                }
619            }
620            "blit_framebuffer" => (g.glBlitFramebuffer)(a(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7), u(8), u(9)),
621            "invalidate_framebuffer" => (g.glInvalidateFramebuffer)(u(0), a(2), at(u(1))),
622            "invalidate_sub_framebuffer" => (g.glInvalidateSubFramebuffer)(u(0), a(2), at(u(1)), a(3), a(4), a(5), a(6)),
623            "read_buffer" => (g.glReadBuffer)(u(0)),
624            "read_pixels" => {
625                let dst = if model.bound_buffer(PIXEL_PACK_BUFFER) != 0 { off(u(6)) as *mut c_void } else { at_mut(u(6)) };
626                (g.glReadPixels)(a(0), a(1), a(2), a(3), u(4), u(5), dst)
627            }
628            "bind_renderbuffer" => (g.glBindRenderbuffer)(u(0), be.obj(Renderbuffer, u(1))),
629            "renderbuffer_storage" => (g.glRenderbufferStorage)(u(0), u(1), a(2), a(3)),
630            "renderbuffer_storage_multisample" => (g.glRenderbufferStorageMultisample)(u(0), a(1), u(2), a(3), a(4)),
631            "get_renderbuffer_parameteriv" => ret(be.int(|p| (g.glGetRenderbufferParameteriv)(u(0), u(1), p)) as i64),
632
633            // ---- shaders and programs ----
634            "shader_source" => {
635                let p = at(u(1));
636                let len = a(2);
637                (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)
638            }
639            "compile_shader" => (g.glCompileShader)(be.obj(Shader, u(0))),
640            "get_shaderiv" => {
641                if u(1) != 0x8B4F && u(1) != 0x8B88 {
642                    ret(be.int(|p| (g.glGetShaderiv)(be.obj(Shader, u(0)), u(1), p)) as i64)
643                }
644            }
645            "get_shader_info_log" => {
646                let log = be.info_log(true, be.obj(Shader, u(0)));
647                ret(text(mem, u(1), u(2), &log)? as i64)
648            }
649            "attach_shader" => (g.glAttachShader)(be.obj(Program, u(0)), be.obj(Shader, u(1))),
650            "detach_shader" => (g.glDetachShader)(be.obj(Program, u(0)), be.obj(Shader, u(1))),
651            "link_program" => {
652                be.uniforms.retain(|(p, _), _| *p != u(0));
653                (g.glLinkProgram)(be.obj(Program, u(0)))
654            }
655            "use_program" => (g.glUseProgram)(be.obj(Program, u(0))),
656            "validate_program" => (g.glValidateProgram)(be.obj(Program, u(0))),
657            "get_programiv" => ret(be.int(|p| (g.glGetProgramiv)(be.obj(Program, u(0)), u(1), p)) as i64),
658            "get_program_info_log" => {
659                let log = be.info_log(false, be.obj(Program, u(0)));
660                ret(text(mem, u(1), u(2), &log)? as i64)
661            }
662            "get_attached_shaders" => {
663                let mut v = [0u32; 16];
664                let mut n = 0i32;
665                (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);
666                let names: Vec<i64> = v[..n.max(0) as usize].iter().map(|&s| be.model_name(Shader, s) as i64).collect();
667                ret(put_i(mem, u(1), u(2), &names, false)? as i64)
668            }
669            "bind_attrib_location" => {
670                let n = c_string(&guest_str(mem, u(2), u(3))?);
671                (g.glBindAttribLocation)(be.obj(Program, u(0)), u(1), n.as_ptr() as *const c_void)
672            }
673            "get_attrib_location" | "get_frag_data_location" => {
674                let n = c_string(&guest_str(mem, u(1), u(2))?);
675                let p = be.obj(Program, u(0));
676                ret(if name == "get_attrib_location" {
677                    (g.glGetAttribLocation)(p, n.as_ptr() as *const c_void)
678                } else {
679                    (g.glGetFragDataLocation)(p, n.as_ptr() as *const c_void)
680                } as i64)
681            }
682            "get_active_attrib" | "get_active_uniform" | "get_transform_feedback_varying" => {
683                let p = be.obj(Program, u(0));
684                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)) =
685                    match name {
686                        "get_active_attrib" => (0x8B89, 0x8B8A, g.glGetActiveAttrib),
687                        "get_active_uniform" => (0x8B86, 0x8B87, g.glGetActiveUniform),
688                        _ => (0x8C83, 0x8C76, g.glGetTransformFeedbackVarying),
689                    };
690                let count = be.int(|q| (g.glGetProgramiv)(p, count_p, q));
691                if u(1) as i64 >= count as i64 {
692                    model.error(INVALID_VALUE);
693                    {
694                    ret(-1);
695                    return Ok(());
696                }
697                }
698                let max = be.int(|q| (g.glGetProgramiv)(p, max_p, q)).max(1);
699                let mut buf = vec![0u8; max as usize];
700                let (mut len, mut size, mut ty) = (0i32, 0i32, 0u32);
701                get(
702                    p, u(1), max, &mut len as *mut i32 as *mut c_void, &mut size as *mut i32 as *mut c_void,
703                    &mut ty as *mut u32 as *mut c_void, buf.as_mut_ptr() as *mut c_void,
704                );
705                let s = String::from_utf8_lossy(&buf[..len.max(0) as usize]).into_owned();
706                put_i(mem, u(4), 2, &[size as i64, ty as i64], false)?;
707                ret(text(mem, u(2), u(3), &s)? as i64)
708            }
709            "get_uniform_location" => {
710                let uname = guest_str(mem, u(1), u(2))?;
711                let p = be.obj(Program, u(0));
712                let c = c_string(&uname);
713                let loc = (g.glGetUniformLocation)(p, c.as_ptr() as *const c_void);
714                let model_loc = model_result as i32;
715                if loc < 0 {
716                    // not in the linked program: -1, and the name gets no location (as in browsers)
717                    if let Some(t) = model.uniforms.get_mut(&u(0)) {
718                        if t.by_name.get(&uname) == Some(&model_loc) && model_loc == t.count - 1 {
719                            t.by_name.remove(&uname);
720                            t.count -= 1;
721                        }
722                    }
723                    {
724                    ret(-1);
725                    return Ok(());
726                }
727                }
728                if !be.uniforms.contains_key(&(u(0), model_loc)) {
729                    let comps = be.uniform_components(p, &uname);
730                    be.uniforms.insert((u(0), model_loc), (loc, comps));
731                }
732            }
733            "get_uniform_index" => {
734                let c = c_string(&guest_str(mem, u(1), u(2))?);
735                let ptr = c.as_ptr();
736                let mut idx = 0u32;
737                (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);
738                ret(idx as i32 as i64)
739            }
740            "get_active_uniformsiv" => {
741                (g.glGetActiveUniformsiv)(be.obj(Program, u(0)), a(2), at(u(1)), u(3), at_mut(u(4)))
742            }
743            "get_uniform_block_index" => {
744                let c = c_string(&guest_str(mem, u(1), u(2))?);
745                ret((g.glGetUniformBlockIndex)(be.obj(Program, u(0)), c.as_ptr() as *const c_void) as i32 as i64)
746            }
747            "get_active_uniform_block_name" => {
748                let p = be.obj(Program, u(0));
749                let n = be.int(|q| (g.glGetActiveUniformBlockiv)(p, u(1), 0x8A41, q)).max(1);
750                let mut buf = vec![0u8; n as usize];
751                let mut len = 0i32;
752                (g.glGetActiveUniformBlockName)(p, u(1), n, &mut len as *mut i32 as *mut c_void, buf.as_mut_ptr() as *mut c_void);
753                let s = String::from_utf8_lossy(&buf[..len.max(0) as usize]).into_owned();
754                ret(text(mem, u(2), u(3), &s)? as i64)
755            }
756            "get_active_uniform_blockiv" => {
757                let p = be.obj(Program, u(0));
758                let n = if u(2) == 0x8A43 { be.int(|q| (g.glGetActiveUniformBlockiv)(p, u(1), 0x8A42, q)).max(0) as usize } else { 1 };
759                let mut v = vec![0i32; n.max(1)];
760                (g.glGetActiveUniformBlockiv)(p, u(1), u(2), v.as_mut_ptr() as *mut c_void);
761                v.truncate(n);
762                let v: Vec<i64> = v.into_iter().map(i64::from).collect();
763                ret(put_i(mem, u(3), u(4), &v, false)? as i64)
764            }
765            "uniform_block_binding" => (g.glUniformBlockBinding)(be.obj(Program, u(0)), u(1), u(2)),
766            "get_uniformfv" | "get_uniformiv" | "get_uniformuiv" => {
767                let Some(&(loc, comps)) = be.uniforms.get(&(u(0), a(1))) else { return Ok(()) };
768                let p = be.obj(Program, u(0));
769                let n = comps.min(16) as usize;
770                if name == "get_uniformfv" {
771                    let mut v = [0f32; 16];
772                    (g.glGetUniformfv)(p, loc, v.as_mut_ptr() as *mut c_void);
773                    ret(put_f(mem, u(2), u(3), &v[..n])? as i64)
774                } else {
775                    let mut v = [0i32; 16];
776                    if name == "get_uniformiv" {
777                        (g.glGetUniformiv)(p, loc, v.as_mut_ptr() as *mut c_void);
778                    } else {
779                        (g.glGetUniformuiv)(p, loc, v.as_mut_ptr() as *mut c_void);
780                    }
781                    let v: Vec<i64> = v[..n].iter().map(|&x| x as i64).collect();
782                    ret(put_i(mem, u(2), u(3), &v, false)? as i64)
783                }
784            }
785            "transform_feedback_varyings" => {
786                let bytes = guest_slice(mem, u(1), u(2) as u64)?;
787                let names: Vec<std::ffi::CString> = bytes
788                    .split(|&b| b == 0)
789                    .take(u(3) as usize)
790                    .map(|s| c_string(&String::from_utf8_lossy(s)))
791                    .collect();
792                let ptrs: Vec<*const c_char> = names.iter().map(|c| c.as_ptr()).collect();
793                (g.glTransformFeedbackVaryings)(be.obj(Program, u(0)), ptrs.len() as i32, ptrs.as_ptr() as *const c_void, u(4))
794            }
795
796            // ---- uniforms (the model checked the location) ----
797            n if n.starts_with("uniform") => {
798                let Some(&(loc, _)) = be.uniforms.get(&(model.program, a(0))) else { return Ok(()) };
799                match n {
800                    "uniform1f" => (g.glUniform1f)(loc, f(1)),
801                    "uniform2f" => (g.glUniform2f)(loc, f(1), f(2)),
802                    "uniform3f" => (g.glUniform3f)(loc, f(1), f(2), f(3)),
803                    "uniform4f" => (g.glUniform4f)(loc, f(1), f(2), f(3), f(4)),
804                    "uniform1i" => (g.glUniform1i)(loc, a(1)),
805                    "uniform2i" => (g.glUniform2i)(loc, a(1), a(2)),
806                    "uniform3i" => (g.glUniform3i)(loc, a(1), a(2), a(3)),
807                    "uniform4i" => (g.glUniform4i)(loc, a(1), a(2), a(3), a(4)),
808                    "uniform1ui" => (g.glUniform1ui)(loc, u(1)),
809                    "uniform2ui" => (g.glUniform2ui)(loc, u(1), u(2)),
810                    "uniform3ui" => (g.glUniform3ui)(loc, u(1), u(2), u(3)),
811                    "uniform4ui" => (g.glUniform4ui)(loc, u(1), u(2), u(3), u(4)),
812                    "uniform1fv" => (g.glUniform1fv)(loc, a(1), at(u(2))),
813                    "uniform2fv" => (g.glUniform2fv)(loc, a(1), at(u(2))),
814                    "uniform3fv" => (g.glUniform3fv)(loc, a(1), at(u(2))),
815                    "uniform4fv" => (g.glUniform4fv)(loc, a(1), at(u(2))),
816                    "uniform1iv" => (g.glUniform1iv)(loc, a(1), at(u(2))),
817                    "uniform2iv" => (g.glUniform2iv)(loc, a(1), at(u(2))),
818                    "uniform3iv" => (g.glUniform3iv)(loc, a(1), at(u(2))),
819                    "uniform4iv" => (g.glUniform4iv)(loc, a(1), at(u(2))),
820                    "uniform1uiv" => (g.glUniform1uiv)(loc, a(1), at(u(2))),
821                    "uniform2uiv" => (g.glUniform2uiv)(loc, a(1), at(u(2))),
822                    "uniform3uiv" => (g.glUniform3uiv)(loc, a(1), at(u(2))),
823                    "uniform4uiv" => (g.glUniform4uiv)(loc, a(1), at(u(2))),
824                    _ => {
825                        let m = match n {
826                            "uniform_matrix2fv" => g.glUniformMatrix2fv,
827                            "uniform_matrix3fv" => g.glUniformMatrix3fv,
828                            "uniform_matrix4fv" => g.glUniformMatrix4fv,
829                            "uniform_matrix2x3fv" => g.glUniformMatrix2x3fv,
830                            "uniform_matrix3x2fv" => g.glUniformMatrix3x2fv,
831                            "uniform_matrix2x4fv" => g.glUniformMatrix2x4fv,
832                            "uniform_matrix4x2fv" => g.glUniformMatrix4x2fv,
833                            "uniform_matrix3x4fv" => g.glUniformMatrix3x4fv,
834                            _ => g.glUniformMatrix4x3fv,
835                        };
836                        m(loc, a(1), (u(2) != 0) as u8, at(u(3)))
837                    }
838                }
839            }
840
841            // ---- queries, transform feedback ----
842            "begin_query" => (g.glBeginQuery)(u(0), be.obj(Query, u(1))),
843            "end_query" => (g.glEndQuery)(u(0)),
844            "get_query_objectuiv" => {
845                // the model says when it's ready (the next frame); GL has the value
846                if u(1) == 0x8866 && model_result != 0 {
847                    let mut v = 0u32;
848                    (g.glGetQueryObjectuiv)(be.obj(Query, u(0)), 0x8866, &mut v as *mut u32 as *mut c_void);
849                    ret(v as i64)
850                }
851            }
852            "bind_transform_feedback" => (g.glBindTransformFeedback)(u(0), be.obj(TransformFeedback, u(1))),
853            "begin_transform_feedback" => (g.glBeginTransformFeedback)(u(0)),
854            "end_transform_feedback" => (g.glEndTransformFeedback)(),
855            "pause_transform_feedback" => (g.glPauseTransformFeedback)(),
856            "resume_transform_feedback" => (g.glResumeTransformFeedback)(),
857            _ => wasmtime::bail!("gasm:gl.{name}: no GL backend for it"),
858        }
859    }
860    Ok(())
861}