gex-gl host: framebuffer and renderbuffer names generated by the host (NVIDIA refuses binding names GenFramebuffers did not return; Hollow Knight drew black on edger)

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_685ed8fb0aed4f7099b7ecc34fc3b595
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-07 14:06:27 -04:00
1 parent cbbe82fff2
commit 273b46359e
3 files changed
+52 -9

No files matched your search

+24 -1
View File
@@ -7,7 +7,8 @@
*
* Object names: the native host runs the guest's buffer, texture and other
* names as they are (a desktop compatibility context and GLES both accept
* names they never generated), so those tables are the identity; programs
* names they never generated), so those tables are the identity --
* except framebuffers and renderbuffers (gexgl_native_object); programs
* and shaders are named by the host's GL, and host.rs tells this side about
* each (gexgl_native_name). Program pipelines are generated by these cases
* themselves and kept here. */
@@ -261,6 +262,28 @@ extern "C" void gexgl_native_pixel_store(uint32_t pname, int32_t param) {
(void)gexgl_set_pixel_store(&pack_store, &unpack_store, pname, param);
}
/* A framebuffer (kind 2) or renderbuffer (kind 3) name of the guest's, as the
* host's: op 0 looks it up, generating a host name at its first use (a
* GenFramebuffers, or a bind of a name the guest never generated), op 1
* forgets it and says which host name to delete. 0 is 0. */
extern "C" uint32_t gexgl_native_object(uint32_t kind, uint32_t guest, uint32_t op) {
if (!guest) return 0;
auto& map = kind == 2 ? framebuffers : renderbuffers;
auto it = map.find(guest);
if (op == 1) {
if (it == map.end()) return guest;
const GLuint host = it->second;
map.erase(it);
return host;
}
if (it != map.end()) return it->second;
GLuint host = 0;
if (kind == 2) glGenFramebuffers(1, &host);
else glGenRenderbuffers(1, &host);
map[guest] = host;
return host;
}
/* A shader (kind 0) or program (kind 1) host.rs created (host != 0) or
* deleted (host == 0). */
extern "C" void gexgl_native_name(uint32_t kind, uint32_t guest, uint32_t host) {
+5
View File
@@ -320,6 +320,11 @@
/* Room for what GL writes even where the table says less. */
std::vector<GLint> values(std::max<size_t>(n, 16));
if (n) glGetIntegerv(pname, values.data());
/* The host's framebuffer and renderbuffer names, as the guest's. */
if (pname == GL_DRAW_FRAMEBUFFER_BINDING || pname == GL_READ_FRAMEBUFFER_BINDING)
values[0] = (GLint)external_name(framebuffers, (GLuint)values[0]);
if (pname == GL_RENDERBUFFER_BINDING)
values[0] = (GLint)external_name(renderbuffers, (GLuint)values[0]);
put_blob(values.data(), n * sizeof(values[0]));
break;
}
+23 -8
View File
@@ -1504,12 +1504,26 @@ impl Gl {
reply.u32(glGetError());
}
// Framebuffers. As for buffers and textures the guest picks
// the names, which binding accepts.
op::GEN_FRAMEBUFFERS | op::GEN_RENDERBUFFERS => {}
op::DELETE_FRAMEBUFFERS | op::DELETE_RENDERBUFFERS => {
// Framebuffers and renderbuffers are named by the host: unlike
// buffers and textures, ARB_framebuffer_object refuses a name
// GenFramebuffers did not return (NVIDIA does, with
// INVALID_OPERATION, and every FBO bind failed: Hollow Knight
// drew black there; Mesa takes them). The decoder keeps the
// map (`gexgl_native_object`), so the generated cases and the
// binding queries see the guest's names.
op::GEN_FRAMEBUFFERS | op::GEN_RENDERBUFFERS => {
let kind = if opcode == op::GEN_FRAMEBUFFERS { 2 } else { 3 };
let n = a.u32().unwrap_or(0);
let names: Vec<GLuint> = (0..n).filter_map(|_| a.u32()).collect();
for _ in 0..n {
if let Some(g) = a.u32() {
native::gexgl_native_object(kind, g, 0);
}
}
}
op::DELETE_FRAMEBUFFERS | op::DELETE_RENDERBUFFERS => {
let kind = if opcode == op::DELETE_FRAMEBUFFERS { 2 } else { 3 };
let n = a.u32().unwrap_or(0);
let names: Vec<GLuint> = (0..n).filter_map(|_| a.u32()).map(|g| native::gexgl_native_object(kind, g, 1)).collect();
if opcode == op::DELETE_FRAMEBUFFERS {
glDeleteFramebuffers(names.len() as GLsizei, names.as_ptr());
} else {
@@ -1518,12 +1532,12 @@ impl Gl {
}
op::BIND_FRAMEBUFFER => {
if let (Some(t), Some(n)) = (a.u32(), a.u32()) {
glBindFramebuffer(t, n);
glBindFramebuffer(t, native::gexgl_native_object(2, n, 0));
}
}
op::BIND_RENDERBUFFER => {
if let (Some(t), Some(n)) = (a.u32(), a.u32()) {
glBindRenderbuffer(t, n);
glBindRenderbuffer(t, native::gexgl_native_object(3, n, 0));
}
}
op::CHECK_FRAMEBUFFER_STATUS => {
@@ -1542,7 +1556,7 @@ impl Gl {
let (Some(t), Some(att), Some(rt), Some(rb)) = (a.u32(), a.u32(), a.u32(), a.u32()) else {
return true;
};
glFramebufferRenderbuffer(t, att, rt, rb);
glFramebufferRenderbuffer(t, att, rt, native::gexgl_native_object(3, rb, 0));
}
op::RENDERBUFFER_STORAGE => {
let (Some(t), Some(f), Some(w), Some(h)) = (a.u32(), a.u32(), a.i32(), a.i32()) else {
@@ -1754,6 +1768,7 @@ mod native {
pub fn gexgl_native_one(op: u32, body: *const u8, len: usize, reply: *mut *const u8, reply_len: *mut usize) -> i32;
pub fn gexgl_native_pixel_store(pname: u32, param: i32);
pub fn gexgl_native_name(kind: u32, guest: u32, host: u32);
pub fn gexgl_native_object(kind: u32, guest: u32, op: u32) -> u32;
}
}