1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
12
13use std::mem::MaybeUninit;
14use wasmtime::{Caller, FuncType, Linker, Val, ValRaw, ValType, bail};
15
16use crate::host::{Host, guest_slice, guest_slice_mut, memory};
17
18#[path = "gl_sigs.rs"]
19mod sigs;
20
21#[path = "gl_backend.rs"]
22mod backend;
23pub use backend::Backend;
24
25const INVALID_ENUM: u32 = 0x0500;
26const INVALID_VALUE: u32 = 0x0501;
27const INVALID_OPERATION: u32 = 0x0502;
28const ARRAY_BUFFER: u32 = 0x8892;
29const ELEMENT_ARRAY_BUFFER: u32 = 0x8893;
30const COPY_READ_BUFFER: u32 = 0x8F36;
31const COPY_WRITE_BUFFER: u32 = 0x8F37;
32const PIXEL_PACK_BUFFER: u32 = 0x88EB;
33const PIXEL_UNPACK_BUFFER: u32 = 0x88EC;
34const TRANSFORM_FEEDBACK_BUFFER: u32 = 0x8C8E;
35const UNIFORM_BUFFER: u32 = 0x8A11;
36const TEXTURE_2D: u32 = 0x0DE1;
37const TEXTURE_CUBE_MAP: u32 = 0x8513;
38const TEXTURE_3D: u32 = 0x806F;
39const TEXTURE_2D_ARRAY: u32 = 0x8C1A;
40const CUBE_POSITIVE_X: u32 = 0x8515;
41const CUBE_NEGATIVE_Z: u32 = 0x851A;
42const TEXTURE0: u32 = 0x84C0;
43const FRAMEBUFFER: u32 = 0x8D40;
44const READ_FRAMEBUFFER: u32 = 0x8CA8;
45const DRAW_FRAMEBUFFER: u32 = 0x8CA9;
46const RENDERBUFFER: u32 = 0x8D41;
47const FRAMEBUFFER_COMPLETE: u32 = 0x8CD5;
48const VERTEX_SHADER: u32 = 0x8B31;
49const FRAGMENT_SHADER: u32 = 0x8B30;
50const COMPILE_STATUS: u32 = 0x8B81;
51const LINK_STATUS: u32 = 0x8B82;
52const VALIDATE_STATUS: u32 = 0x8B83;
53const SHADER_SOURCE_LENGTH: u32 = 0x8B88;
54const SHADER_TYPE: u32 = 0x8B4F;
55const TRANSFORM_FEEDBACK: u32 = 0x8E22;
56const SYNC_STATUS: u32 = 0x9114;
57const SIGNALED: u32 = 0x9119;
58const UNSIGNALED: u32 = 0x9118;
59const ALREADY_SIGNALED: u32 = 0x911A;
60const TIMEOUT_EXPIRED: u32 = 0x911B;
61const WAIT_FAILED: u32 = 0x911D;
62const SYNC_GPU_COMMANDS_COMPLETE: u32 = 0x9117;
63const QUERY_RESULT: u32 = 0x8866;
64const QUERY_RESULT_AVAILABLE: u32 = 0x8867;
65const CURRENT_QUERY: u32 = 0x8865;
66const INVALID_INDEX: u32 = 0xFFFF_FFFF;
67
68const BUFFER_TARGETS: &[u32] = &[
69 ARRAY_BUFFER,
70 ELEMENT_ARRAY_BUFFER,
71 COPY_READ_BUFFER,
72 COPY_WRITE_BUFFER,
73 PIXEL_PACK_BUFFER,
74 PIXEL_UNPACK_BUFFER,
75 TRANSFORM_FEEDBACK_BUFFER,
76 UNIFORM_BUFFER,
77];
78const TEXTURE_TARGETS: &[u32] = &[TEXTURE_2D, TEXTURE_CUBE_MAP, TEXTURE_3D, TEXTURE_2D_ARRAY];
79const VOLUME_TARGETS: &[u32] = &[TEXTURE_3D, TEXTURE_2D_ARRAY];
80const FB_TARGETS: &[u32] = &[FRAMEBUFFER, READ_FRAMEBUFFER, DRAW_FRAMEBUFFER];
81const QUERY_TARGETS: &[u32] = &[0x8C2F, 0x8D6A, 0x8C88];
82const INDEXED_TARGETS: &[u32] = &[UNIFORM_BUFFER, TRANSFORM_FEEDBACK_BUFFER];
83
84pub fn pixel_bytes(format: u32, ty: u32) -> u32 {
86 match ty {
87 0x8363 | 0x8033 | 0x8034 => return 2,
88 0x8368 | 0x8C3B | 0x8C3E | 0x84FA => return 4,
89 0x8DAD => return 8,
90 _ => {}
91 }
92 let size = match ty {
93 0x1400 | 0x1401 => 1,
94 0x1402 | 0x1403 | 0x140B => 2,
95 0x1404..=0x1406 => 4,
96 _ => 0,
97 };
98 let comps = match format {
99 0x1903 | 0x8D94 | 0x1906 | 0x1909 | 0x1902 => 1,
100 0x8227 | 0x8228 | 0x190A | 0x84F9 => 2,
101 0x1907 | 0x8D98 => 3,
102 0x1908 | 0x8D99 => 4,
103 _ => 0,
104 };
105 size * comps
106}
107
108#[derive(Clone, Copy)]
109struct Store {
110 alignment: i64,
111 row_length: i64,
112 image_height: i64,
113 skip_pixels: i64,
114 skip_rows: i64,
115 skip_images: i64,
116}
117
118const STORE: Store = Store {
119 alignment: 4,
120 row_length: 0,
121 image_height: 0,
122 skip_pixels: 0,
123 skip_rows: 0,
124 skip_images: 0,
125};
126
127fn image_bytes(w: i64, h: i64, d: i64, bpp: i64, s: &Store) -> i64 {
129 if w <= 0 || h <= 0 || d <= 0 {
130 return 0;
131 }
132 let row_len = if s.row_length > 0 { s.row_length } else { w };
133 let a = s.alignment;
134 let row = (row_len * bpp + a - 1) / a * a;
135 let img_h = if s.image_height > 0 {
136 s.image_height
137 } else {
138 h
139 };
140 let img = row * img_h;
141 s.skip_images * img
142 + (d - 1) * img
143 + s.skip_rows * row
144 + (h - 1) * row
145 + s.skip_pixels * bpp
146 + w * bpp
147}
148
149fn null_limit(pname: u32) -> &'static [i64] {
151 match pname {
153 0x0D33 | 0x851C | 0x84E8 => &[2048], 0x8073 | 0x88FF => &[256], 0x8872 | 0x8B4C | 0x8869 => &[16], 0x8B4D => &[32], 0x8DFB => &[256], 0x8DFD => &[224], 0x8DFC => &[15], 0x8B4B | 0x9125 => &[60], 0x9122 => &[64], 0x8B4A => &[1024], 0x8B49 => &[896], 0x8CDF | 0x8824 | 0x8D57 => &[4], 0x8A2F => &[24], 0x8A30 => &[16384], 0x8A34 => &[256], 0x8A2B | 0x8A2D => &[12], 0x8A2E => &[24], 0x8A31 => &[50176], 0x8A33 => &[50048], 0x8904 => &[-8], 0x8905 => &[7], 0x84FD => &[2], 0x8D6B => &[16777215], 0x8C8A => &[64], 0x8C8B | 0x8C80 => &[4], 0x0D3A => &[4096, 4096], 0x846D | 0x846E => &[1, 1], 0x821B => &[3], _ => &[0],
182 }
183}
184
185fn null_string(name: u32) -> Option<&'static str> {
186 Some(match name {
187 0x1F00 => "gasm",
188 0x1F01 => "gasm null GL",
189 0x1F02 => "OpenGL ES 3.0 (gasm null GL)",
190 0x8B8C => "OpenGL ES GLSL ES 3.00 (gasm null GL)",
191 0x1F03 => "",
192 _ => return None,
193 })
194}
195
196#[derive(Clone, Copy, PartialEq, Eq, Hash)]
197enum Kind {
198 Buffer,
199 Texture,
200 VertexArray,
201 Sampler,
202 Framebuffer,
203 Renderbuffer,
204 Shader,
205 Program,
206 Query,
207 Sync,
208 TransformFeedback,
209}
210
211#[derive(Default)]
212struct Uniforms {
213 by_name: HashMap<String, i32>,
214 count: i32,
215}
216
217pub struct Gl {
219 pub stats: Option<GlStats>,
221 next: HashMap<Kind, u32>,
222 live: HashMap<Kind, HashSet<u32>>,
223 errors: Vec<u32>,
224 buffers: HashMap<u32, u32>,
225 textures: HashMap<(u32, u32), u32>,
226 texture_targets: HashMap<u32, u32>,
228 unit: u32,
229 framebuffers: HashMap<u32, u32>,
230 renderbuffer: u32,
231 program: u32,
232 vertex_array: u32,
233 vaos: HashMap<u32, Vao>,
235 buffer_users: HashMap<u32, HashSet<(u32, u8)>>,
238 unpack: Store,
239 pack: Store,
240 shaders: HashMap<u32, (u32, String)>,
241 uniforms: HashMap<u32, Uniforms>,
242 active: HashMap<u32, u32>,
243 query_ended: HashMap<u32, u64>,
244 sync_made: HashMap<u32, u64>,
245 error_count: u64,
247 backend: Option<Box<Backend>>,
249}
250
251#[derive(Default)]
254pub struct GlStats {
255 calls: HashMap<&'static str, u64>,
256 frames: Option<(u64, u64)>,
257}
258
259impl GlStats {
260 fn count(&mut self, name: &'static str, frame: u64) {
261 *self.calls.entry(name).or_default() += 1;
262 let (first, _) = self.frames.unwrap_or((frame, frame));
263 self.frames = Some((first, frame));
264 }
265
266 pub fn report(&self) -> String {
268 let frames = self.frames.map_or(1, |(a, b)| b - a + 1) as f64;
269 let mut calls: Vec<_> = self.calls.iter().map(|(&n, &c)| (n, c)).collect();
270 calls.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
271 let total: u64 = calls.iter().map(|c| c.1).sum();
272 let mut out = format!("[gasm] gl stats: {:.1} calls a frame over {frames} frames", total as f64 / frames);
273 for (n, c) in calls {
274 out += &format!("\n {:>9.1} {n}", c as f64 / frames);
275 }
276 out
277 }
278}
279
280impl Drop for GlStats {
281 fn drop(&mut self) {
282 eprintln!("{}", self.report());
283 }
284}
285
286const MAX_ATTRIBS: usize = 16;
288const ELEMENTS: usize = MAX_ATTRIBS;
290
291#[derive(Clone, Copy, Default)]
295struct Vao {
296 elements: u32,
297 attribs: [(bool, u32); MAX_ATTRIBS],
298}
299
300impl Default for Gl {
301 fn default() -> Self {
302 Gl {
303 stats: None,
304 next: HashMap::default(),
305 live: HashMap::default(),
306 errors: Vec::new(),
307 buffers: HashMap::default(),
308 textures: HashMap::default(),
309 texture_targets: HashMap::default(),
310 unit: 0,
311 framebuffers: HashMap::default(),
312 renderbuffer: 0,
313 program: 0,
314 vertex_array: 0,
315 vaos: HashMap::from_iter([(0, Vao::default())]),
316 buffer_users: HashMap::default(),
317 unpack: STORE,
318 pack: STORE,
319 shaders: HashMap::default(),
320 uniforms: HashMap::default(),
321 active: HashMap::default(),
322 query_ended: HashMap::default(),
323 sync_made: HashMap::default(),
324 error_count: 0,
325 backend: None,
326 }
327 }
328}
329
330impl Gl {
331 fn error(&mut self, e: u32) -> bool {
332 self.error_count += 1;
333 if self.errors.len() < 32 {
334 self.errors.push(e);
335 }
336 false
337 }
338 fn get_error(&mut self) -> u32 {
339 if self.errors.is_empty() {
340 0
341 } else {
342 self.errors.remove(0)
343 }
344 }
345 fn create(&mut self, k: Kind) -> u32 {
346 let next = self.next.entry(k).or_insert(1);
347 let n = *next;
348 *next += 1;
349 self.live.entry(k).or_default().insert(n);
350 n
351 }
352 fn has(&self, k: Kind, n: u32) -> bool {
353 self.live.get(&k).is_some_and(|s| s.contains(&n))
354 }
355 fn valid(&mut self, k: Kind, n: u32, allow_zero: bool) -> bool {
357 if n == 0 {
358 return allow_zero || self.error(INVALID_VALUE);
359 }
360 self.has(k, n) || self.error(INVALID_OPERATION)
361 }
362 fn remove(&mut self, k: Kind, n: u32) {
363 if n != 0 {
364 if let Some(s) = self.live.get_mut(&k) {
365 s.remove(&n);
366 }
367 }
368 }
369 fn is(&self, k: Kind, n: u32) -> i32 {
370 (n != 0 && self.has(k, n)) as i32
371 }
372 fn target(&mut self, list: &[u32], t: u32) -> bool {
373 list.contains(&t) || self.error(INVALID_ENUM)
374 }
375 fn image_target(&mut self, t: u32) -> bool {
376 t == TEXTURE_2D
377 || (CUBE_POSITIVE_X..=CUBE_NEGATIVE_Z).contains(&t)
378 || self.error(INVALID_ENUM)
379 }
380 fn bound_buffer(&self, t: u32) -> u32 {
381 self.buffers.get(&t).copied().unwrap_or(0)
382 }
383 fn vao_element(&self) -> u32 {
384 self.vaos.get(&self.vertex_array).map_or(0, |v| v.elements)
385 }
386 fn set_vao_buffer(&mut self, vao: u32, slot: usize, n: u32) {
388 let Some(v) = self.vaos.get_mut(&vao) else { return };
389 let old = std::mem::replace(if slot == ELEMENTS { &mut v.elements } else { &mut v.attribs[slot].1 }, n);
390 if old == n {
391 return;
392 }
393 if let Some(users) = self.buffer_users.get_mut(&old) {
394 users.remove(&(vao, slot as u8));
395 if users.is_empty() {
396 self.buffer_users.remove(&old);
397 }
398 }
399 if n != 0 {
400 self.buffer_users.entry(n).or_default().insert((vao, slot as u8));
401 }
402 }
403 fn bound_texture(&self, t: u32) -> u32 {
404 let t = if (CUBE_POSITIVE_X..=CUBE_NEGATIVE_Z).contains(&t) {
405 TEXTURE_CUBE_MAP
406 } else {
407 t
408 };
409 self.textures.get(&(self.unit, t)).copied().unwrap_or(0)
410 }
411 fn need_texture(&mut self, t: u32) -> bool {
412 self.bound_texture(t) != 0 || self.error(INVALID_OPERATION)
413 }
414 fn attrib(&mut self, i: u32) -> Option<usize> {
416 if i as usize >= MAX_ATTRIBS {
417 self.error(INVALID_VALUE);
418 return None;
419 }
420 Some(i as usize)
421 }
422 fn attribs_ready(&mut self) -> bool {
424 let missing = self.vaos.get(&self.vertex_array).is_some_and(|v| v.attribs.iter().any(|&(on, b)| on && b == 0));
425 !missing || self.error(INVALID_OPERATION)
426 }
427 fn non_negative(&mut self, v: &[i32]) -> bool {
428 v.iter().all(|&x| x >= 0) || self.error(INVALID_VALUE)
429 }
430 fn buffer_bound(&mut self, t: u32) -> bool {
431 if !self.target(BUFFER_TARGETS, t) {
432 return false;
433 }
434 let b = if t == ELEMENT_ARRAY_BUFFER {
435 self.vao_element()
436 } else {
437 self.bound_buffer(t)
438 };
439 b != 0 || self.error(INVALID_OPERATION)
440 }
441 fn need_renderbuffer(&mut self, t: u32) -> bool {
442 (self.target(&[RENDERBUFFER], t) && self.renderbuffer != 0)
443 || (t == RENDERBUFFER && self.error(INVALID_OPERATION))
444 }
445 fn pixel_store(&mut self, pname: u32, v: i32) -> bool {
446 let v = v as i64;
447 let (unpack, field): (bool, fn(&mut Store) -> &mut i64) = match pname {
448 0x0CF5 => (true, |s| &mut s.alignment),
449 0x0D05 => (false, |s| &mut s.alignment),
450 0x0CF2 => (true, |s| &mut s.row_length),
451 0x806E => (true, |s| &mut s.image_height),
452 0x0CF4 => (true, |s| &mut s.skip_pixels),
453 0x0CF3 => (true, |s| &mut s.skip_rows),
454 0x806D => (true, |s| &mut s.skip_images),
455 0x0D02 => (false, |s| &mut s.row_length),
456 0x0D04 => (false, |s| &mut s.skip_pixels),
457 0x0D03 => (false, |s| &mut s.skip_rows),
458 _ => return true,
459 };
460 let alignment = pname == 0x0CF5 || pname == 0x0D05;
461 if if alignment {
462 ![1, 2, 4, 8].contains(&v)
463 } else {
464 v < 0
465 } {
466 return self.error(INVALID_VALUE);
467 }
468 *field(if unpack {
469 &mut self.unpack
470 } else {
471 &mut self.pack
472 }) = v;
473 true
474 }
475 fn model_param(&self, pname: u32) -> Option<i64> {
477 let tex = |t| self.bound_texture(t) as i64;
478 Some(match pname {
479 0x8894 => self.bound_buffer(ARRAY_BUFFER) as i64,
480 0x8895 => self.vao_element() as i64,
481 0x8F36 => self.bound_buffer(COPY_READ_BUFFER) as i64,
482 0x8F37 => self.bound_buffer(COPY_WRITE_BUFFER) as i64,
483 0x88ED => self.bound_buffer(PIXEL_PACK_BUFFER) as i64,
484 0x88EF => self.bound_buffer(PIXEL_UNPACK_BUFFER) as i64,
485 0x8A28 => self.bound_buffer(UNIFORM_BUFFER) as i64,
486 0x8C8F => self.bound_buffer(TRANSFORM_FEEDBACK_BUFFER) as i64,
487 0x8B8D => self.program as i64,
488 0x8069 => tex(TEXTURE_2D),
489 0x8514 => tex(TEXTURE_CUBE_MAP),
490 0x806A => tex(TEXTURE_3D),
491 0x8C1D => tex(TEXTURE_2D_ARRAY),
492 0x84E0 => (TEXTURE0 + self.unit) as i64,
493 0x8CA6 => self
494 .framebuffers
495 .get(&DRAW_FRAMEBUFFER)
496 .copied()
497 .unwrap_or(0) as i64,
498 0x8CAA => self
499 .framebuffers
500 .get(&READ_FRAMEBUFFER)
501 .copied()
502 .unwrap_or(0) as i64,
503 0x8CA7 => self.renderbuffer as i64,
504 0x85B5 => self.vertex_array as i64,
505 0x0CF5 => self.unpack.alignment,
506 0x0D05 => self.pack.alignment,
507 0x0CF2 => self.unpack.row_length,
508 0x806E => self.unpack.image_height,
509 0x0CF4 => self.unpack.skip_pixels,
510 0x0CF3 => self.unpack.skip_rows,
511 0x806D => self.unpack.skip_images,
512 0x0D02 => self.pack.row_length,
513 0x0D04 => self.pack.skip_pixels,
514 0x0D03 => self.pack.skip_rows,
515 _ => return None,
516 })
517 }
518 fn params(&self, pname: u32) -> Vec<i64> {
519 match self.model_param(pname) {
520 Some(v) => vec![v],
521 None => null_limit(pname).to_vec(),
522 }
523 }
524 fn uniform_target(&mut self, loc: i32) -> bool {
526 if loc == -1 {
527 return false;
528 }
529 if self.program == 0 {
530 return self.error(INVALID_OPERATION);
531 }
532 let known = self
533 .uniforms
534 .get(&self.program)
535 .is_some_and(|u| loc >= 0 && loc < u.count);
536 known || self.error(INVALID_OPERATION)
537 }
538}
539
540pub(crate) fn add_gl_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
542 let engine = linker.engine().clone();
543 for &(name, params, result) in sigs::FUNCTIONS {
544 let ty = |c: char| match c {
545 'i' => ValType::I32,
546 'I' => ValType::I64,
547 'f' => ValType::F32,
548 _ => unreachable!("gl_sigs.rs: type {c}"),
549 };
550 let ft = FuncType::new(&engine, params.chars().map(ty), result.chars().map(ty));
551 let f32_result = result == "f";
552 assert!(params.len() <= MAX_PARAMS && result.len() <= 1, "gl_sigs.rs: {name}");
553 let zero = |c: u8| match c {
554 b'I' => Val::I64(0),
555 b'f' => Val::F32(0),
556 _ => Val::I32(0),
557 };
558 unsafe {
564 linker.func_new_unchecked("gasm:gl", name, ft, move |mut c, raw| {
565 let mut args = [Val::I32(0); MAX_PARAMS];
566 for (i, t) in params.bytes().enumerate() {
567 let r = raw[i].assume_init();
568 args[i] = match t {
569 b'I' => Val::I64(r.get_i64()),
570 b'f' => Val::F32(r.get_f32()),
571 _ => Val::I32(r.get_i32()),
572 };
573 }
574 let mut res = [result.bytes().next().map_or(Val::I32(0), zero)];
575 let results = &mut res[..result.len()];
576 call(&mut c, name, f32_result, &args[..params.len()], results)?;
577 if let Some(v) = results.first() {
578 raw[0] = MaybeUninit::new(match *v {
579 Val::I64(x) => ValRaw::i64(x),
580 Val::F32(b) => ValRaw::f32(b),
581 Val::I32(x) => ValRaw::i32(x),
582 _ => unreachable!("gasm:gl results are numbers"),
583 });
584 }
585 Ok(())
586 })?;
587 }
588 }
589 Ok(())
590}
591
592const MAX_PARAMS: usize = 12;
594
595fn put(mem: &mut [u8], dst: u32, count: u32, values: &[i64], kind: char) -> wasmtime::Result<i32> {
597 let n = values.len().min(count as usize);
598 let size = if kind == 'I' { 8 } else { 4 };
599 if n > 0 {
600 let out = guest_slice_mut(mem, dst, (n * size) as u64)?;
601 for (i, &v) in values[..n].iter().enumerate() {
602 match kind {
603 'f' => out[i * 4..i * 4 + 4].copy_from_slice(&(v as f32).to_le_bytes()),
604 'I' => out[i * 8..i * 8 + 8].copy_from_slice(&v.to_le_bytes()),
605 _ => out[i * 4..i * 4 + 4].copy_from_slice(&(v as i32).to_le_bytes()),
606 }
607 }
608 }
609 Ok(values.len() as i32)
610}
611
612fn text(mem: &mut [u8], dst: u32, cap: u32, s: &str) -> wasmtime::Result<i32> {
613 if s.len() <= cap as usize {
614 guest_slice_mut(mem, dst, s.len() as u64)?.copy_from_slice(s.as_bytes());
615 }
616 Ok(s.len() as i32)
617}
618
619fn guest_str(mem: &[u8], ptr: u32, len: u32) -> wasmtime::Result<String> {
620 let b = guest_slice(mem, ptr, len as u64)?;
621 std::str::from_utf8(b)
622 .map(str::to_owned)
623 .map_err(|_| wasmtime::format_err!("string argument is not UTF-8"))
624}
625
626fn call(c: &mut Caller<'_, Host>, name: &'static str, f32_result: bool, args: &[Val], results: &mut [Val]) -> wasmtime::Result<()> {
630 let frame = c.data().frame_index;
631 if let Some(s) = &mut c.data_mut().gl.stats {
632 s.count(name, frame);
633 }
634 let before = c.data().gl.error_count;
635 model_call(c, name, f32_result, args, results)?;
636 if c.data().gl.backend.is_none() || c.data().gl.error_count != before {
637 return Ok(());
638 }
639 let mem = memory(c)?;
640 let (mem, host) = mem.data_and_store_mut(c);
641 let gl = &mut host.gl;
642 let mut be = gl.backend.take().expect("backend");
643 let r = backend::forward(&mut be, gl, name, args, results, mem);
644 gl.backend = Some(be);
645 r
646}
647
648fn model_call(
650 c: &mut Caller<'_, Host>,
651 name: &str,
652 f32_result: bool,
653 args: &[Val],
654 results: &mut [Val],
655) -> wasmtime::Result<()> {
656 let mem = memory(c)?;
657 let (mem, host) = mem.data_and_store_mut(c);
658 let hashing = host.hashing;
659 let frame = host.frame_index;
660 let catch_up = host.catch_up;
661 let (gl, hash) = (&mut host.gl, &mut host.video_hash);
662 let a = |i: usize| -> i32 {
663 match args[i] {
664 Val::I32(v) => v,
665 Val::I64(v) => v as i32,
666 Val::F32(b) => b as i32,
667 _ => 0,
668 }
669 };
670 let u = |i: usize| a(i) as u32;
671 let mut fold = |header: &[u32], payload: Option<&[u8]>| {
672 if !hashing {
673 return;
674 }
675 for v in header {
676 hash.update(&v.to_le_bytes());
677 }
678 if let Some(p) = payload {
679 hash.update(p);
680 }
681 };
682 if let Some(r) = results.first_mut() {
684 *r = if f32_result { Val::F32(0) } else { Val::I32(0) };
685 }
686 let mut ret = |v: i64| {
687 if let Some(r) = results.first_mut() {
688 *r = match r {
689 Val::F32(_) => Val::F32((v as f32).to_bits()),
690 _ => Val::I32(v as i32),
691 };
692 }
693 };
694 use Kind::*;
695 match name {
696 "width" => ret(1280),
698 "height" => ret(720),
699 "frame_shown" => ret(!catch_up as i64),
701 "get_error" => ret(gl.get_error() as i64),
702 "get_string" => match null_string(u(0)) {
703 Some(s) => ret(text(mem, u(1), u(2), s)? as i64),
704 None => {
705 gl.error(INVALID_ENUM);
706 ret(-1)
707 }
708 },
709 "enable_extension" => {
710 guest_str(mem, u(0), u(1))?;
711 ret(0)
712 }
713 "get_integerv" | "get_floatv" | "get_integer64v" => {
714 let v = gl.params(u(0));
715 let kind = match name {
716 "get_floatv" => 'f',
717 "get_integer64v" => 'I',
718 _ => 'i',
719 };
720 ret(put(mem, u(1), u(2), &v, kind)? as i64)
721 }
722 "get_integeri_v" => ret(put(mem, u(2), u(3), &[0], 'i')? as i64),
723 "get_internalformativ" => {
724 ret(put(mem, u(3), u(4), &[if u(2) == 0x9380 { 1 } else { 4 }], 'i')? as i64)
725 }
726 "get_shader_precision_format" => {
727 put(mem, u(2), 3, &[127, 127, 23], 'i')?;
728 }
729 "active_texture" => {
730 let unit = u(0).wrapping_sub(TEXTURE0);
731 if unit >= 32 {
732 gl.error(INVALID_ENUM);
733 } else {
734 gl.unit = unit;
735 }
736 }
737 "is_enabled" => ret(0),
738 "pixel_storei" => {
739 gl.pixel_store(u(0), a(1));
740 }
741 "scissor" | "viewport" => {
742 gl.non_negative(&[a(2), a(3)]);
743 }
744 "present"
746 | "blend_color"
747 | "blend_equation"
748 | "blend_equation_separate"
749 | "blend_func"
750 | "blend_func_separate"
751 | "clear"
752 | "clear_color"
753 | "clear_depthf"
754 | "clear_stencil"
755 | "color_mask"
756 | "cull_face"
757 | "depth_func"
758 | "depth_mask"
759 | "depth_rangef"
760 | "disable"
761 | "enable"
762 | "front_face"
763 | "hint"
764 | "line_width"
765 | "polygon_offset"
766 | "sample_coverage"
767 | "stencil_func"
768 | "stencil_func_separate"
769 | "stencil_mask"
770 | "stencil_mask_separate"
771 | "stencil_op"
772 | "stencil_op_separate"
773 | "finish"
774 | "flush"
775 | "vertex_attrib_divisor"
776 | "vertex_attrib4f"
777 | "vertex_attribi4i"
778 | "vertex_attribi4ui"
779 | "clear_bufferfi"
780 | "blit_framebuffer"
781 | "read_buffer"
782 | "begin_transform_feedback"
783 | "end_transform_feedback"
784 | "pause_transform_feedback"
785 | "resume_transform_feedback" => {}
786
787 "create_buffer" => ret(gl.create(Buffer) as i64),
789 "delete_buffer" => {
790 let n = u(0);
791 gl.buffers.retain(|_, b| *b != n);
792 for (v, slot) in gl.buffer_users.remove(&n).unwrap_or_default() {
794 if let Some(v) = gl.vaos.get_mut(&v) {
795 *(if slot as usize == ELEMENTS { &mut v.elements } else { &mut v.attribs[slot as usize].1 }) = 0;
796 }
797 }
798 gl.remove(Buffer, n);
799 }
800 "is_buffer" => ret(gl.is(Buffer, u(0)) as i64),
801 "bind_buffer" => {
802 let (t, n) = (u(0), u(1));
803 if gl.target(BUFFER_TARGETS, t) && gl.valid(Buffer, n, true) {
804 if t == ELEMENT_ARRAY_BUFFER {
805 gl.set_vao_buffer(gl.vertex_array, ELEMENTS, n);
806 } else {
807 gl.buffers.insert(t, n);
808 }
809 }
810 }
811 "bind_buffer_base" | "bind_buffer_range" => {
812 let (t, n) = (u(0), u(2));
813 if gl.target(INDEXED_TARGETS, t) && gl.valid(Buffer, n, true) {
814 gl.buffers.insert(t, n);
815 }
816 }
817 "buffer_data" => {
818 let (t, ptr, len) = (u(0), u(1), u(2));
819 let data = if ptr != 0 {
820 Some(guest_slice(mem, ptr, len as u64)?)
821 } else {
822 None
823 };
824 if gl.buffer_bound(t) {
825 fold(&[1, t, 0, len], data);
826 }
827 }
828 "buffer_sub_data" => {
829 let (t, off, ptr, len) = (u(0), u(1), u(2), u(3));
830 let data = guest_slice(mem, ptr, len as u64)?;
831 if gl.buffer_bound(t) {
832 fold(&[1, t, off, len], Some(data));
833 }
834 }
835 "copy_buffer_sub_data" => {
836 let _ = gl.buffer_bound(u(0)) && gl.buffer_bound(u(1));
837 }
838 "get_buffer_sub_data" => {
839 let out = guest_slice_mut(mem, u(2), u(3) as u64)?;
840 if gl.buffer_bound(u(0)) {
841 out.fill(0);
842 }
843 }
844 "get_buffer_parameteriv" => {
845 gl.buffer_bound(u(0));
846 ret(0)
847 }
848
849 "create_vertex_array" => {
851 let n = gl.create(VertexArray);
852 gl.vaos.insert(n, Vao::default());
853 ret(n as i64)
854 }
855 "delete_vertex_array" => {
856 let n = u(0);
857 if gl.vertex_array == n {
858 gl.vertex_array = 0;
859 }
860 if n != 0 && gl.vaos.contains_key(&n) {
861 for slot in 0..=ELEMENTS {
862 gl.set_vao_buffer(n, slot, 0);
863 }
864 gl.vaos.remove(&n);
865 }
866 gl.remove(VertexArray, n);
867 }
868 "is_vertex_array" => ret(gl.is(VertexArray, u(0)) as i64),
869 "bind_vertex_array" => {
870 if gl.valid(VertexArray, u(0), true) {
871 gl.vertex_array = u(0);
872 }
873 }
874 "vertex_attrib_pointer" | "vertex_attrib_ipointer" => {
875 let off = if name == "vertex_attrib_pointer" {
876 a(5)
877 } else {
878 a(4)
879 };
880 let buffer = gl.bound_buffer(ARRAY_BUFFER);
881 if let Some(i) = gl.attrib(u(0)) {
882 if buffer == 0 && off != 0 {
883 gl.error(INVALID_OPERATION);
884 } else {
885 gl.set_vao_buffer(gl.vertex_array, i, buffer);
886 }
887 }
888 }
889 "enable_vertex_attrib_array" | "disable_vertex_attrib_array" => {
890 let on = name == "enable_vertex_attrib_array";
891 if let Some(i) = gl.attrib(u(0)) {
892 if let Some(v) = gl.vaos.get_mut(&gl.vertex_array) {
893 v.attribs[i].0 = on;
894 }
895 }
896 }
897 "get_vertex_attribiv" | "get_vertex_attrib_offset" => ret(0),
898 "get_vertex_attribfv" => ret(put(mem, u(2), u(3), &[0, 0, 0, 1], 'f')? as i64),
899
900 "draw_arrays" => {
902 let _ = gl.non_negative(&[a(1), a(2)]) && gl.attribs_ready();
903 }
904 "draw_elements" => {
905 let _ = gl.non_negative(&[a(1)]) && gl.attribs_ready();
906 }
907 "draw_arrays_instanced" => {
908 let _ = gl.non_negative(&[a(1), a(2), a(3)]) && gl.attribs_ready();
909 }
910 "draw_elements_instanced" => {
911 let _ = gl.non_negative(&[a(1), a(4)]) && gl.attribs_ready();
912 }
913 "draw_range_elements" => {
914 let _ = gl.non_negative(&[a(3)]) && (a(2) >= a(1) || gl.error(INVALID_VALUE)) && gl.attribs_ready();
915 }
916 "draw_buffers" => {
917 guest_slice(mem, u(0), u(1) as u64 * 4)?;
918 }
919 "clear_bufferiv" | "clear_bufferuiv" | "clear_bufferfv" => {
920 guest_slice(mem, u(2), u(3) as u64 * 4)?;
921 }
922
923 "create_texture" => ret(gl.create(Texture) as i64),
925 "delete_texture" => {
926 let n = u(0);
927 gl.textures.retain(|_, t| *t != n);
928 gl.texture_targets.remove(&n);
929 gl.remove(Texture, n);
930 }
931 "is_texture" => ret(gl.is(Texture, u(0)) as i64),
932 "bind_texture" => {
933 let (t, n) = (u(0), u(1));
934 if gl.target(TEXTURE_TARGETS, t) && gl.valid(Texture, n, true) {
935 if n != 0 && *gl.texture_targets.entry(n).or_insert(t) != t {
936 gl.error(INVALID_OPERATION);
937 } else {
938 gl.textures.insert((gl.unit, t), n);
939 }
940 }
941 }
942 "tex_parameteri"
943 | "tex_parameterf"
944 | "get_tex_parameteriv"
945 | "get_tex_parameterfv"
946 | "generate_mipmap" => {
947 let _ = gl.target(TEXTURE_TARGETS, u(0)) && gl.need_texture(u(0));
948 ret(0)
949 }
950 "tex_image_2d" | "tex_image_3d" | "tex_sub_image_2d" | "tex_sub_image_3d" => {
951 let t = u(0);
953 let sub = name.starts_with("tex_sub");
954 let three = name.ends_with("3d");
955 let (ifmt, x, y, z, w, h, d, fi) = match (sub, three) {
956 (false, false) => (u(2), 0, 0, 0, a(3), a(4), 1, 6),
957 (false, true) => (u(2), 0, 0, 0, a(3), a(4), a(5), 7),
958 (true, false) => (0, a(2), a(3), 0, a(4), a(5), 1, 6),
959 (true, true) => (0, a(2), a(3), a(4), a(5), a(6), a(7), 8),
960 };
961 let (format, ty, ptr, len) = (u(fi), u(fi + 1), u(fi + 2), u(fi + 3));
962 let level = a(1);
963 let ok = if three {
964 gl.target(VOLUME_TARGETS, t)
965 } else {
966 gl.image_target(t)
967 } && gl.need_texture(t)
968 && gl.non_negative(&[level, x, y, z, w, h, d]);
969 if !ok {
970 return Ok(());
971 }
972 let unpack = gl.bound_buffer(PIXEL_UNPACK_BUFFER) != 0;
974 let mut data = None;
975 if !unpack && ptr != 0 {
976 let bpp = pixel_bytes(format, ty);
977 if bpp == 0 {
978 gl.error(INVALID_ENUM);
979 return Ok(());
980 }
981 let need = image_bytes(w as i64, h as i64, d as i64, bpp as i64, &gl.unpack);
982 if (len as i64) < need {
983 bail!(
984 "gasm:gl: {len} bytes of pixels for a {w}x{h}x{d} image that needs {need}"
985 );
986 }
987 data = Some(guest_slice(mem, ptr, len as u64)?);
988 }
989 if sub && data.is_none() && !unpack {
990 gl.error(INVALID_VALUE);
991 return Ok(());
992 }
993 let n = if data.is_some() { len } else { 0 };
994 fold(
995 &[
996 2,
997 t,
998 level as u32,
999 ifmt,
1000 x as u32,
1001 y as u32,
1002 z as u32,
1003 w as u32,
1004 h as u32,
1005 d as u32,
1006 format,
1007 ty,
1008 n,
1009 ],
1010 data,
1011 );
1012 }
1013 "tex_storage_2d" | "tex_storage_3d" => {
1014 let t = u(0);
1015 let three = name.ends_with("3d");
1016 let list: &[u32] = if three {
1017 VOLUME_TARGETS
1018 } else {
1019 &[TEXTURE_2D, TEXTURE_CUBE_MAP]
1020 };
1021 let positive = a(1) > 0 && a(3) > 0 && a(4) > 0 && (!three || a(5) > 0);
1022 let _ =
1023 gl.target(list, t) && gl.need_texture(t) && (positive || gl.error(INVALID_VALUE));
1024 }
1025 "compressed_tex_image_2d"
1026 | "compressed_tex_image_3d"
1027 | "compressed_tex_sub_image_2d"
1028 | "compressed_tex_sub_image_3d" => {
1029 let t = u(0);
1030 let sub = name.contains("sub");
1031 let three = name.ends_with("3d");
1032 let (ifmt, x, y, z, w, h, d, fmt, pi) = match (sub, three) {
1034 (false, false) => (u(2), 0, 0, 0, a(3), a(4), 1, u(2), 6),
1035 (false, true) => (u(2), 0, 0, 0, a(3), a(4), a(5), u(2), 7),
1036 (true, false) => (0, a(2), a(3), 0, a(4), a(5), 1, u(6), 7),
1037 (true, true) => (0, a(2), a(3), a(4), a(5), a(6), a(7), u(8), 9),
1038 };
1039 let (ptr, len) = (u(pi), u(pi + 1));
1040 let data = guest_slice(mem, ptr, len as u64)?;
1041 let level = a(1);
1042 let ok = if three {
1043 gl.target(VOLUME_TARGETS, t)
1044 } else {
1045 gl.image_target(t)
1046 } && gl.need_texture(t)
1047 && {
1048 let v: &[i32] = if sub {
1049 &[level, x, y, z, w, h, d]
1050 } else {
1051 &[level, w, h, d]
1052 };
1053 gl.non_negative(v)
1054 };
1055 if ok {
1056 fold(
1057 &[
1058 2,
1059 t,
1060 level as u32,
1061 ifmt,
1062 x as u32,
1063 y as u32,
1064 z as u32,
1065 w as u32,
1066 h as u32,
1067 d as u32,
1068 fmt,
1069 0,
1070 len,
1071 ],
1072 Some(data),
1073 );
1074 }
1075 }
1076 "copy_tex_image_2d" | "copy_tex_sub_image_2d" => {
1077 let _ = gl.image_target(u(0)) && gl.need_texture(u(0));
1078 }
1079 "copy_tex_sub_image_3d" => {
1080 gl.need_texture(u(0));
1081 }
1082
1083 "create_sampler" => ret(gl.create(Sampler) as i64),
1085 "delete_sampler" => gl.remove(Sampler, u(0)),
1086 "is_sampler" => ret(gl.is(Sampler, u(0)) as i64),
1087 "bind_sampler" => {
1088 gl.valid(Sampler, u(1), true);
1089 }
1090 "sampler_parameteri"
1091 | "sampler_parameterf"
1092 | "get_sampler_parameteriv"
1093 | "get_sampler_parameterfv" => {
1094 gl.valid(Sampler, u(0), false);
1095 ret(0)
1096 }
1097
1098 "create_framebuffer" => ret(gl.create(Framebuffer) as i64),
1100 "delete_framebuffer" => {
1101 let n = u(0);
1102 gl.framebuffers.retain(|_, f| *f != n);
1103 gl.remove(Framebuffer, n);
1104 }
1105 "is_framebuffer" => ret(gl.is(Framebuffer, u(0)) as i64),
1106 "bind_framebuffer" => {
1107 let (t, n) = (u(0), u(1));
1108 if gl.target(FB_TARGETS, t) && gl.valid(Framebuffer, n, true) {
1109 if t == FRAMEBUFFER {
1110 gl.framebuffers.insert(DRAW_FRAMEBUFFER, n);
1111 gl.framebuffers.insert(READ_FRAMEBUFFER, n);
1112 } else {
1113 gl.framebuffers.insert(t, n);
1114 }
1115 }
1116 }
1117 "check_framebuffer_status" => ret(if gl.target(FB_TARGETS, u(0)) {
1118 FRAMEBUFFER_COMPLETE as i64
1119 } else {
1120 0
1121 }),
1122 "framebuffer_texture_2d" => {
1123 let _ = gl.target(FB_TARGETS, u(0)) && gl.valid(Texture, u(3), true);
1124 }
1125 "framebuffer_texture_layer" => {
1126 let _ = gl.target(FB_TARGETS, u(0)) && gl.valid(Texture, u(2), true);
1127 }
1128 "framebuffer_renderbuffer" => {
1129 let _ = gl.target(FB_TARGETS, u(0)) && gl.valid(Renderbuffer, u(3), true);
1130 }
1131 "get_framebuffer_attachment_parameteriv" => {
1132 gl.target(FB_TARGETS, u(0));
1133 ret(0)
1134 }
1135 "invalidate_framebuffer" | "invalidate_sub_framebuffer" => {
1136 guest_slice(mem, u(1), u(2) as u64 * 4)?;
1137 }
1138 "read_pixels" => {
1139 let (w, h, format, ty, dst, len) = (a(2), a(3), u(4), u(5), u(6), u(7));
1140 if !gl.non_negative(&[w, h]) || gl.bound_buffer(PIXEL_PACK_BUFFER) != 0 {
1141 return Ok(());
1142 }
1143 let bpp = pixel_bytes(format, ty);
1144 if bpp == 0 {
1145 gl.error(INVALID_ENUM);
1146 return Ok(());
1147 }
1148 let need = image_bytes(w as i64, h as i64, 1, bpp as i64, &gl.pack);
1149 if (len as i64) < need {
1150 bail!("gasm:gl: read_pixels: {len} bytes for {w}x{h} that needs {need}");
1151 }
1152 guest_slice_mut(mem, dst, len as u64)?[..need as usize].fill(0);
1153 }
1154 "create_renderbuffer" => ret(gl.create(Renderbuffer) as i64),
1155 "delete_renderbuffer" => {
1156 if gl.renderbuffer == u(0) {
1157 gl.renderbuffer = 0;
1158 }
1159 gl.remove(Renderbuffer, u(0));
1160 }
1161 "is_renderbuffer" => ret(gl.is(Renderbuffer, u(0)) as i64),
1162 "bind_renderbuffer" => {
1163 if gl.target(&[RENDERBUFFER], u(0)) && gl.valid(Renderbuffer, u(1), true) {
1164 gl.renderbuffer = u(1);
1165 }
1166 }
1167 "renderbuffer_storage" => {
1168 let _ = gl.need_renderbuffer(u(0)) && gl.non_negative(&[a(2), a(3)]);
1169 }
1170 "renderbuffer_storage_multisample" => {
1171 let _ = gl.need_renderbuffer(u(0)) && gl.non_negative(&[a(1), a(3), a(4)]);
1172 }
1173 "get_renderbuffer_parameteriv" => {
1174 gl.need_renderbuffer(u(0));
1175 ret(0)
1176 }
1177
1178 "create_shader" => {
1180 let ty = u(0);
1181 if ty != VERTEX_SHADER && ty != FRAGMENT_SHADER {
1182 gl.error(INVALID_ENUM);
1183 ret(0);
1184 return Ok(());
1185 }
1186 let n = gl.create(Shader);
1187 gl.shaders.insert(n, (ty, String::new()));
1188 ret(n as i64)
1189 }
1190 "delete_shader" => gl.remove(Shader, u(0)),
1191 "is_shader" => ret(gl.is(Shader, u(0)) as i64),
1192 "shader_source" => {
1193 let src = guest_str(mem, u(1), u(2))?;
1194 if gl.valid(Shader, u(0), false) {
1195 if let Some(s) = gl.shaders.get_mut(&u(0)) {
1196 s.1 = src;
1197 }
1198 }
1199 }
1200 "compile_shader" => {
1201 gl.valid(Shader, u(0), false);
1202 }
1203 "get_shaderiv" => {
1204 if !gl.valid(Shader, u(0), false) {
1205 ret(0);
1206 return Ok(());
1207 }
1208 let (ty, src) = gl.shaders.get(&u(0)).cloned().unwrap_or_default();
1209 ret(match u(1) {
1210 SHADER_TYPE => ty as i64,
1211 SHADER_SOURCE_LENGTH if !src.is_empty() => src.len() as i64 + 1,
1212 COMPILE_STATUS => 1,
1213 _ => 0,
1214 })
1215 }
1216 "get_shader_info_log" => ret(if gl.valid(Shader, u(0), false) {
1217 text(mem, u(1), u(2), "")? as i64
1218 } else {
1219 -1
1220 }),
1221 "get_shader_source" => {
1222 if gl.valid(Shader, u(0), false) {
1223 let src = gl
1224 .shaders
1225 .get(&u(0))
1226 .map(|s| s.1.clone())
1227 .unwrap_or_default();
1228 ret(text(mem, u(1), u(2), &src)? as i64)
1229 } else {
1230 ret(-1)
1231 }
1232 }
1233 "create_program" => ret(gl.create(Program) as i64),
1234 "delete_program" => {
1235 let n = u(0);
1236 if gl.program == n {
1237 gl.program = 0;
1238 }
1239 gl.uniforms.remove(&n);
1240 gl.remove(Program, n);
1241 }
1242 "is_program" => ret(gl.is(Program, u(0)) as i64),
1243 "attach_shader" | "detach_shader" => {
1244 let _ = gl.valid(Program, u(0), false) && gl.valid(Shader, u(1), false);
1245 }
1246 "link_program" => {
1247 if gl.valid(Program, u(0), false) {
1248 gl.uniforms.remove(&u(0)); }
1250 }
1251 "use_program" => {
1252 if gl.valid(Program, u(0), true) {
1253 gl.program = u(0);
1254 }
1255 }
1256 "validate_program" | "uniform_block_binding" => {
1257 gl.valid(Program, u(0), false);
1258 }
1259 "get_programiv" => {
1260 let ok = gl.valid(Program, u(0), false);
1261 ret((ok && (u(1) == LINK_STATUS || u(1) == VALIDATE_STATUS)) as i64)
1262 }
1263 "get_program_info_log" | "get_active_uniform_block_name" => {
1264 let (dst, cap) = if name == "get_program_info_log" {
1265 (u(1), u(2))
1266 } else {
1267 (u(2), u(3))
1268 };
1269 ret(if gl.valid(Program, u(0), false) {
1270 text(mem, dst, cap, "")? as i64
1271 } else {
1272 -1
1273 })
1274 }
1275 "get_attached_shaders" => {
1276 if gl.valid(Program, u(0), false) {
1277 put(mem, u(1), u(2), &[], 'i')?;
1278 }
1279 ret(0)
1280 }
1281 "bind_attrib_location" => {
1282 guest_str(mem, u(2), u(3))?;
1283 gl.valid(Program, u(0), false);
1284 }
1285 "get_attrib_location" | "get_frag_data_location" => {
1286 guest_str(mem, u(1), u(2))?;
1287 ret(if gl.valid(Program, u(0), false) {
1288 0
1289 } else {
1290 -1
1291 })
1292 }
1293 "get_active_attrib" | "get_active_uniform" | "get_transform_feedback_varying" => {
1294 gl.valid(Program, u(0), false);
1295 ret(-1)
1296 }
1297 "get_uniform_location" => {
1299 let name = guest_str(mem, u(1), u(2))?;
1300 if !gl.valid(Program, u(0), false) {
1301 ret(-1);
1302 return Ok(());
1303 }
1304 let table = gl.uniforms.entry(u(0)).or_default();
1305 let loc = match table.by_name.get(&name) {
1306 Some(&l) => l,
1307 None => {
1308 let l = table.count;
1309 table.count += 1;
1310 table.by_name.insert(name, l);
1311 l
1312 }
1313 };
1314 ret(loc as i64)
1315 }
1316 "get_uniform_index" | "get_uniform_block_index" => {
1317 guest_str(mem, u(1), u(2))?;
1318 ret(if gl.valid(Program, u(0), false) {
1319 0
1320 } else {
1321 INVALID_INDEX as i32 as i64
1322 })
1323 }
1324 "get_active_uniformsiv" => {
1325 let count = u(2);
1326 guest_slice(mem, u(1), count as u64 * 4)?;
1327 if gl.valid(Program, u(0), false) {
1328 put(mem, u(4), count, &vec![0; count as usize], 'i')?;
1329 }
1330 }
1331 "get_active_uniform_blockiv" => ret(if gl.valid(Program, u(0), false) {
1332 put(mem, u(3), u(4), &[0], 'i')? as i64
1333 } else {
1334 -1
1335 }),
1336 "get_uniformfv" | "get_uniformiv" | "get_uniformuiv" => {
1337 if !gl.valid(Program, u(0), false) {
1338 ret(-1);
1339 return Ok(());
1340 }
1341 let loc = a(1);
1342 if !gl
1343 .uniforms
1344 .get(&u(0))
1345 .is_some_and(|t| loc >= 0 && loc < t.count)
1346 {
1347 gl.error(INVALID_OPERATION);
1348 ret(-1);
1349 return Ok(());
1350 }
1351 ret(put(
1352 mem,
1353 u(2),
1354 u(3),
1355 &[0],
1356 if name == "get_uniformfv" { 'f' } else { 'i' },
1357 )? as i64)
1358 }
1359 "transform_feedback_varyings" => {
1360 let bytes = guest_slice(mem, u(1), u(2) as u64)?;
1361 let pieces = bytes.iter().filter(|&&b| b == 0).count() as i64 + 1;
1362 if a(3) as i64 > pieces {
1363 bail!("gasm:gl: transform_feedback_varyings: fewer names than count");
1364 }
1365 gl.valid(Program, u(0), false);
1366 }
1367
1368 n if n.starts_with("uniform") && n != "uniform_block_binding" => {
1370 let (kind, comps, vector, matrix) = uniform_kind(n);
1371 let loc = a(0);
1372 if !vector {
1373 if !gl.uniform_target(loc) {
1374 return Ok(());
1375 }
1376 let mut bytes = Vec::with_capacity(16);
1377 for v in &args[1..] {
1378 match *v {
1379 Val::F32(b) => bytes.extend_from_slice(&b.to_le_bytes()),
1380 Val::I32(i) => bytes.extend_from_slice(&i.to_le_bytes()),
1381 _ => {}
1382 }
1383 }
1384 fold(&[3, loc as u32, kind, 1], Some(&bytes));
1385 return Ok(());
1386 }
1387 let count = a(1);
1388 if count < 0 {
1389 gl.error(INVALID_VALUE);
1390 return Ok(());
1391 }
1392 let ptr = if matrix { u(3) } else { u(2) };
1393 let bytes = guest_slice(mem, ptr, count as u64 * comps as u64 * 4)?;
1394 if !gl.uniform_target(loc) {
1395 return Ok(());
1396 }
1397 if matrix {
1398 fold(
1399 &[3, loc as u32, kind, count as u32, (a(2) != 0) as u32],
1400 Some(bytes),
1401 );
1402 } else {
1403 fold(&[3, loc as u32, kind, count as u32], Some(bytes));
1404 }
1405 }
1406
1407 "create_query" => ret(gl.create(Query) as i64),
1409 "delete_query" => gl.remove(Query, u(0)),
1410 "is_query" => ret(gl.is(Query, u(0)) as i64),
1411 "begin_query" => {
1412 let (t, n) = (u(0), u(1));
1413 if gl.target(QUERY_TARGETS, t) && gl.valid(Query, n, false) {
1414 gl.active.insert(t, n);
1415 gl.query_ended.remove(&n);
1416 }
1417 }
1418 "end_query" => {
1419 let t = u(0);
1420 if gl.target(QUERY_TARGETS, t) {
1421 match gl.active.remove(&t) {
1422 Some(n) if n != 0 => {
1423 gl.query_ended.insert(n, frame);
1424 }
1425 _ => {
1426 gl.error(INVALID_OPERATION);
1427 }
1428 }
1429 }
1430 }
1431 "get_queryiv" => {
1432 let ok = gl.target(QUERY_TARGETS, u(0)) && u(1) == CURRENT_QUERY;
1433 ret(if ok {
1434 gl.active.get(&u(0)).copied().unwrap_or(0) as i64
1435 } else {
1436 0
1437 })
1438 }
1439 "get_query_objectuiv" => {
1440 let n = u(0);
1441 if !gl.valid(Query, n, false) {
1442 ret(0);
1443 return Ok(());
1444 }
1445 let ready = gl.query_ended.get(&n).is_some_and(|&e| frame > e);
1446 ret(match u(1) {
1447 QUERY_RESULT_AVAILABLE => ready as i64,
1448 QUERY_RESULT => ready as i64, _ => {
1450 gl.error(INVALID_ENUM);
1451 0
1452 }
1453 })
1454 }
1455 "fence_sync" => {
1456 let (cond, flags) = (u(0), u(1));
1457 if cond != SYNC_GPU_COMMANDS_COMPLETE || flags != 0 {
1458 gl.error(if cond != SYNC_GPU_COMMANDS_COMPLETE {
1459 INVALID_ENUM
1460 } else {
1461 INVALID_VALUE
1462 });
1463 ret(0);
1464 return Ok(());
1465 }
1466 let n = gl.create(Sync);
1467 gl.sync_made.insert(n, frame);
1468 ret(n as i64)
1469 }
1470 "is_sync" => ret(gl.is(Sync, u(0)) as i64),
1471 "delete_sync" => {
1472 gl.sync_made.remove(&u(0));
1473 gl.remove(Sync, u(0));
1474 }
1475 "client_wait_sync" => {
1476 let n = u(0);
1477 if !gl.valid(Sync, n, false) {
1478 ret(WAIT_FAILED as i64);
1479 return Ok(());
1480 }
1481 let done = gl.sync_made.get(&n).is_some_and(|&m| frame > m);
1482 ret((if done {
1483 ALREADY_SIGNALED
1484 } else {
1485 TIMEOUT_EXPIRED
1486 }) as i64)
1487 }
1488 "wait_sync" => {
1489 gl.valid(Sync, u(0), false);
1490 }
1491 "get_synciv" => {
1492 let n = u(0);
1493 if !gl.valid(Sync, n, false) {
1494 ret(0);
1495 return Ok(());
1496 }
1497 let done = gl.sync_made.get(&n).is_some_and(|&m| frame > m);
1498 ret(if u(1) != SYNC_STATUS {
1499 0
1500 } else if done {
1501 SIGNALED as i64
1502 } else {
1503 UNSIGNALED as i64
1504 })
1505 }
1506 "create_transform_feedback" => ret(gl.create(TransformFeedback) as i64),
1507 "delete_transform_feedback" => gl.remove(TransformFeedback, u(0)),
1508 "is_transform_feedback" => ret(gl.is(TransformFeedback, u(0)) as i64),
1509 "bind_transform_feedback" => {
1510 let _ =
1511 gl.target(&[TRANSFORM_FEEDBACK], u(0)) && gl.valid(TransformFeedback, u(1), true);
1512 }
1513 _ => bail!("gasm:gl.{name}: not implemented by this runner"),
1514 }
1515 Ok(())
1516}
1517
1518fn uniform_kind(name: &str) -> (u32, u32, bool, bool) {
1521 const MATRICES: [(&str, u32, u32); 9] = [
1522 ("2fv", 32, 4),
1523 ("3fv", 33, 9),
1524 ("4fv", 34, 16),
1525 ("2x3fv", 35, 6),
1526 ("3x2fv", 36, 6),
1527 ("2x4fv", 37, 8),
1528 ("4x2fv", 38, 8),
1529 ("3x4fv", 39, 12),
1530 ("4x3fv", 40, 12),
1531 ];
1532 if let Some(m) = name.strip_prefix("uniform_matrix") {
1533 let &(_, kind, comps) = MATRICES
1534 .iter()
1535 .find(|(s, _, _)| *s == m)
1536 .expect("matrix uniform");
1537 return (kind, comps, true, true);
1538 }
1539 let rest = &name["uniform".len()..];
1540 let n: u32 = rest[..1].parse().unwrap_or(1);
1541 let suffix = &rest[1..];
1542 let base = match suffix.trim_end_matches('v') {
1543 "f" => 0,
1544 "i" => 10,
1545 _ => 20, };
1547 (base + n, n, suffix.ends_with('v'), false)
1548}
1549
1550#[cfg(test)]
1551mod tests {
1552 use super::*;
1553
1554 #[test]
1555 fn max_attribs_is_the_null_limit() {
1556 assert_eq!(null_limit(0x8869)[0], MAX_ATTRIBS as i64);
1557 }
1558}