From bbe813ecdcad0ac005f615538af671363479dcb9 Mon Sep 17 00:00:00 2001 From: atomchild411 <143453386+atomchild411@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:08:06 -0800 Subject: [PATCH] iris-hostgl: a texture's internal format is answered as the program gave it glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT) answered with the sized format the Mac chose -- GL_RGBA8 for GL_RGBA -- where IRIX's libGL (and Mesa) answer with what the program asked for. Cosmo Player probes texture sizes with GL_PROXY_TEXTURE_2D and compares that answer with the format it asked for; on the Mac's it took every size to be refused and shrank every texture to 1x1, so LivingO2 (the O2 demos, VRML in Netscape) drew each texture as one texel: the monitor's screen dark, the desk flat orange, the logos solid rectangles. The executor keeps the internal format each glTexImage1D/2D/3D and glCopyTexImage1D/2D gave, by texture, target and level (a proxy's under texture 0), and answers with it while the host has an image there; a refused proxy (0) is left alone. Generated by tools/glshim.py. Test: texture_internal_format_is_answered_as_the_program_gave_it (fails without). Co-Authored-By: Claude Opus 5.5 --- iris-hostgl/src/calls.rs | 10 +++++++ iris-hostgl/src/exec.rs | 53 +++++++++++++++++++++++++++++++++++++ iris-hostgl/src/tests.rs | 25 +++++++++++++++++ iris-hostgl/tools/glshim.py | 10 +++++++ 4 files changed, 98 insertions(+) diff --git a/iris-hostgl/src/calls.rs b/iris-hostgl/src/calls.rs index f6421b1a..76bd8696 100644 --- a/iris-hostgl/src/calls.rs +++ b/iris-hostgl/src/calls.rs @@ -1779,6 +1779,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(87)?; let g: extern "C" fn(u32, i32, u32, i32, i32, i32, i32) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as u32, a3, a4, a5, a6); + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } @@ -1794,6 +1795,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(88)?; let g: extern "C" fn(u32, i32, u32, i32, i32, i32, i32) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as u32, a3, a4, a5, a6); + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } @@ -1810,6 +1812,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(89)?; let g: extern "C" fn(u32, i32, u32, i32, i32, i32, i32, i32) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as u32, a3, a4, a5, a6, a7); + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } @@ -1826,6 +1829,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(90)?; let g: extern "C" fn(u32, i32, u32, i32, i32, i32, i32, i32) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as u32, a3, a4, a5, a6, a7); + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } @@ -3164,6 +3168,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(209)?; let g: extern "C" fn(u32, i32, u32, *mut f32) = unsafe { std::mem::transmute(f) }; g(a0, a1, a2, o3.as_mut_ptr()); + x.texture_internal_answer_f(a0, a1, a2, &mut o3); x.put::(a3, &o3[..n3])?; x.image_done(); Some(0) @@ -3179,6 +3184,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(210)?; let g: extern "C" fn(u32, i32, u32, *mut i32) = unsafe { std::mem::transmute(f) }; g(a0, a1, a2, o3.as_mut_ptr()); + x.texture_internal_answer(a0, a1, a2, &mut o3); x.put::(a3, &o3[..n3])?; x.image_done(); Some(0) @@ -5336,6 +5342,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(424)?; let g: extern "C" fn(u32, i32, i32, i32, i32, u32, u32, *const c_void) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as i32, a3, a4, x.gl_format(a5), x.gl_type(a5, a6), p7 as *const c_void); + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } @@ -5355,6 +5362,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let g: extern "C" fn(u32, i32, i32, i32, i32, i32, u32, u32, *const c_void) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as i32, a3, x.gl_height(a4 as i32) as i32, a5, x.gl_format(a6), x.gl_type(a6, a7), p8 as *const c_void); } + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } @@ -5373,6 +5381,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(426)?; let g: extern "C" fn(u32, i32, u32, i32, i32, i32, i32, u32, u32, *const c_void) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as u32, a3, x.gl_height(a4 as i32) as i32, a5, a6, x.gl_format(a7), x.gl_type(a7, a8), p9 as *const c_void); + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } @@ -5391,6 +5400,7 @@ pub fn run(x: &mut Exec, op: usize, c: &[u8]) -> Option { let f = x.func(427)?; let g: extern "C" fn(u32, i32, u32, i32, i32, i32, i32, u32, u32, *const c_void) = unsafe { std::mem::transmute(f) }; g(a0, a1, x.gl_internal(a2 as i32) as u32, a3, x.gl_height(a4 as i32) as i32, a5, a6, x.gl_format(a7), x.gl_type(a7, a8), p9 as *const c_void); + x.note_internal(a0 as u32, a1 as i32, a2 as i32); x.image_done(); Some(0) } diff --git a/iris-hostgl/src/exec.rs b/iris-hostgl/src/exec.rs index 07c1c3f7..c1b3788d 100644 --- a/iris-hostgl/src/exec.rs +++ b/iris-hostgl/src/exec.rs @@ -105,6 +105,10 @@ pub struct ClientSide { /// whenever the context's drawables are bound (draw::bind_with). pub draw_buffer: Option, pub read_buffer: Option, + /// The internal format each texture image was given, as the program + /// named it, by (texture, target, level); a proxy target's under texture + /// 0. See `texture_internal_answer`. + pub asked_internal: HashMap<(u32, u32, i32), i32>, } #[derive(Default, Clone, Copy)] @@ -343,6 +347,22 @@ fn relight_after_clip_plane() { } } +/// The texture bound to `target` on the host, 0 for a proxy target, or None +/// for a target not kept (see Exec::note_internal). +fn texture_of(target: u32) -> Option { + let binding = match target { + 0x0DE0 => 0x8068, // GL_TEXTURE_1D: GL_TEXTURE_BINDING_1D + 0x0DE1 => 0x8069, // GL_TEXTURE_2D + 0x806F => 0x806A, // GL_TEXTURE_3D + 0x8063 | 0x8064 | 0x8070 => return Some(0), // the proxies + _ => return None, + }; + let mut name = 0i32; + // SAFETY: a query of one integer. + unsafe { glGetIntegerv(binding, &mut name) }; + Some(name as u32) +} + impl<'a> Exec<'a> { #[allow(clippy::too_many_arguments)] pub fn new( @@ -762,6 +782,39 @@ impl<'a> Exec<'a> { self.client.emul.select_format(tex, internal).unwrap_or(internal) } + /// A texture image was given `internal` by the program (glTexImage*, + /// glCopyTexImage*): kept for glGetTexLevelParameter's answer. + pub fn note_internal(&mut self, target: u32, level: i32, internal: i32) { + let Some(tex) = texture_of(target) else { return }; + self.client.asked_internal.insert((tex, target, level), internal); + } + + /// GL_TEXTURE_INTERNAL_FORMAT (GL_TEXTURE_COMPONENTS) is answered with + /// the internal format the program asked for, as IRIX's libGL answers it + /// (and Mesa's), not the sized one the Mac picked: GL_RGBA, not GL_RGBA8. + /// Cosmo Player checks a proxy texture's answer against what it asked + /// for, and on the Mac's took every texture size to be refused: LivingO2 + /// (the O2 demos) drew each texture as one texel. A level the host has no + /// image for (a refused proxy: 0) is left as it answered. + pub fn texture_internal_answer(&self, target: u32, level: i32, pname: u32, answer: &mut [i32]) { + const GL_TEXTURE_INTERNAL_FORMAT: u32 = 0x1003; + if pname != GL_TEXTURE_INTERNAL_FORMAT || answer.first().is_none_or(|&a| a == 0) { + return; + } + let Some(tex) = texture_of(target) else { return }; + if let Some(&asked) = self.client.asked_internal.get(&(tex, target, level)) { + answer[0] = asked; + } + } + + pub fn texture_internal_answer_f(&self, target: u32, level: i32, pname: u32, answer: &mut [f32]) { + let mut i = [answer.first().map_or(0, |&a| a as i32)]; + self.texture_internal_answer(target, level, pname, &mut i); + if let Some(a) = answer.first_mut() { + *a = i[0] as f32; + } + } + /// The pixel format to give the host for the one the guest named. /// /// Only `GL_ABGR_EXT` differs, and `image` has already put the data in the diff --git a/iris-hostgl/src/tests.rs b/iris-hostgl/src/tests.rs index 8f90462d..332854d5 100644 --- a/iris-hostgl/src/tests.rs +++ b/iris-hostgl/src/tests.rs @@ -1709,3 +1709,28 @@ fn lit_drawing_after_a_clip_plane_goes_off_is_drawn() { let p = g.mem.get(px, 4).to_vec(); assert!(p[1] > 200 && p[2] < 50, "the quad drawn lit after the plane went off: {p:?}"); } + +/// glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT) answers with the +/// internal format the program gave, as IRIX's libGL does, not the sized one +/// the host chose: Cosmo Player compares a proxy's answer with what it asked +/// for, and on GL_RGBA8 for GL_RGBA shrank every texture to 1x1 (LivingO2). +#[test] +fn texture_internal_format_is_answered_as_the_program_gave_it() { + let mut g = Guest::new(512); + g.context(0x0040_000d, 16, 16); + let out = g.mem.alloc(16, 0); + let px = g.mem.alloc(4 * 4 * 4, 0); + g.mem.put(px, &[0x80u8; 4 * 4 * 4]); + let mut e = Enc::default(); + // A proxy, as Cosmo probes: GL_PROXY_TEXTURE_2D, GL_RGBA. + e.cmd("glTexImage2D", &[V::I(0x8064), V::I(0), V::I(0x1908), V::I(64), V::I(64), V::I(0), V::I(0x1908), V::I(0x1401), V::A(0)]) + .cmd("glGetTexLevelParameteriv", &[V::I(0x8064), V::I(0), V::I(0x1003), V::A(out)]) + // A real texture with GL 1.0's "3 components". + .cmd("glBindTexture", &[V::I(0x0DE1), V::I(5)]) + .cmd("glTexImage2D", &[V::I(0x0DE1), V::I(0), V::I(3), V::I(4), V::I(4), V::I(0), V::I(0x1908), V::I(0x1401), V::A(px)]) + .cmd("glGetTexLevelParameteriv", &[V::I(0x0DE1), V::I(0), V::I(0x1003), V::A(out + 4)]); + assert_eq!(g.batch(&e.take()), Reply::Ok(0, 0)); + let v = |i: u64| u32::from_be_bytes(g.mem.get(out + i, 4).try_into().unwrap()); + assert_eq!(v(0), 0x1908, "the proxy: GL_RGBA, as asked"); + assert_eq!(v(4), 3, "the texture: 3, as given"); +} diff --git a/iris-hostgl/tools/glshim.py b/iris-hostgl/tools/glshim.py index 291800a5..fa451af8 100644 --- a/iris-hostgl/tools/glshim.py +++ b/iris-hostgl/tools/glshim.py @@ -503,6 +503,16 @@ def gen_rust(api): lines.append(f"g({', '.join(call_args)});") if guard: lines.append("}") + # The internal format a texture image was given, as the + # program named it, for glGetTexLevelParameter to answer + # with (Exec::texture_internal_answer). + if internal_params and "target" in local and "level" in local: + ik = next(k for k, p in enumerate(params) if p["name"] in internal_params) + lines.append(f"x.note_internal({local['target']} as u32, {local['level']} as i32, a{ik} as i32);") + if name == "glGetTexLevelParameteriv": + lines.append("x.texture_internal_answer(a0, a1, a2, &mut o3);") + if name == "glGetTexLevelParameterfv": + lines.append("x.texture_internal_answer_f(a0, a1, a2, &mut o3);") # The state queries go past the executor on the way back, so # that a limit the host reports but this library cannot stand # behind is answered with one it can (Exec::get_limit).