diff --git a/hw/display/meson.build b/hw/display/meson.build
index ffecedb..4414781 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -143,3 +143,8 @@ endif
 system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_lcdc.c'))
 
 modules += { 'hw-display': hw_display_modules }
+
+# qegl: OpenGL pass-through for the Mac OS X 10.4 guest (G:\Tiger\qe), Windows hosts only
+if host_os == 'windows' and opengl.found()
+  system_ss.add(when: 'CONFIG_PCI', if_true: [files('qegl.c', 'qegl-gl.c', 'qegl-gl3.c'), opengl])
+endif
diff --git a/hw/display/qegl-gl-gen.inc b/hw/display/qegl-gl-gen.inc
new file mode 100644
index 0000000..c7a40e1
--- /dev/null
+++ b/hw/display/qegl-gl-gen.inc
@@ -0,0 +1,4661 @@
+/* Generated by gen/gen_gl.py - do not edit. GL pass-through decoders. */
+case 0: { /* accum */
+    if (!qegl_gl_avail[0]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glAccum(a0, a1);
+    return 0;
+}
+case 1: { /* alpha_func */
+    if (!qegl_gl_avail[1]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glAlphaFunc(a0, a1);
+    return 0;
+}
+case 2: { /* are_textures_resident */
+    if (!qegl_gl_avail[2]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    void *a2 = rd_out(&r, 16);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glAreTexturesResident(a0, (const GLuint *) a1, (GLboolean *) t2);
+    memcpy(ro, &rv, 4);
+    memcpy(a2, t2, 16);
+    return 0;
+}
+case 4: { /* begin */
+    if (!qegl_gl_avail[4]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBegin(a0);
+    return 0;
+}
+case 5: { /* bind_texture */
+    if (!qegl_gl_avail[5]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindTexture(a0, a1);
+    return 0;
+}
+case 7: { /* blend_func */
+    if (!qegl_gl_avail[7]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBlendFunc(a0, a1);
+    return 0;
+}
+case 8: { /* call_list */
+    if (!qegl_gl_avail[8]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCallList(a0);
+    return 0;
+}
+case 10: { /* clear */
+    if (!qegl_gl_avail[10]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClear(a0);
+    return 0;
+}
+case 11: { /* clear_accum */
+    if (!qegl_gl_avail[11]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearAccum(a0, a1, a2, a3);
+    return 0;
+}
+case 12: { /* clear_color */
+    if (!qegl_gl_avail[12]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearColor(a0, a1, a2, a3);
+    return 0;
+}
+case 13: { /* clear_depth */
+    if (!qegl_gl_avail[13]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearDepth(a0);
+    return 0;
+}
+case 14: { /* clear_index */
+    if (!qegl_gl_avail[14]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearIndex(a0);
+    return 0;
+}
+case 15: { /* clear_stencil */
+    if (!qegl_gl_avail[15]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearStencil(a0);
+    return 0;
+}
+case 16: { /* clip_plane */
+    if (!qegl_gl_avail[16]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClipPlane(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 17: { /* color3b */
+    if (!qegl_gl_avail[17]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3b(a0, a1, a2);
+    return 0;
+}
+case 18: { /* color3bv */
+    if (!qegl_gl_avail[18]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3bv((const GLbyte *) a0);
+    return 0;
+}
+case 19: { /* color3d */
+    if (!qegl_gl_avail[19]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3d(a0, a1, a2);
+    return 0;
+}
+case 20: { /* color3dv */
+    if (!qegl_gl_avail[20]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3dv((const GLdouble *) a0);
+    return 0;
+}
+case 21: { /* color3f */
+    if (!qegl_gl_avail[21]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3f(a0, a1, a2);
+    return 0;
+}
+case 22: { /* color3fv */
+    if (!qegl_gl_avail[22]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3fv((const GLfloat *) a0);
+    return 0;
+}
+case 23: { /* color3i */
+    if (!qegl_gl_avail[23]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3i(a0, a1, a2);
+    return 0;
+}
+case 24: { /* color3iv */
+    if (!qegl_gl_avail[24]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3iv((const GLint *) a0);
+    return 0;
+}
+case 25: { /* color3s */
+    if (!qegl_gl_avail[25]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3s(a0, a1, a2);
+    return 0;
+}
+case 26: { /* color3sv */
+    if (!qegl_gl_avail[26]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3sv((const GLshort *) a0);
+    return 0;
+}
+case 27: { /* color3ub */
+    if (!qegl_gl_avail[27]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3ub(a0, a1, a2);
+    return 0;
+}
+case 28: { /* color3ubv */
+    if (!qegl_gl_avail[28]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3ubv((const GLubyte *) a0);
+    return 0;
+}
+case 29: { /* color3ui */
+    if (!qegl_gl_avail[29]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3ui(a0, a1, a2);
+    return 0;
+}
+case 30: { /* color3uiv */
+    if (!qegl_gl_avail[30]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3uiv((const GLuint *) a0);
+    return 0;
+}
+case 31: { /* color3us */
+    if (!qegl_gl_avail[31]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3us(a0, a1, a2);
+    return 0;
+}
+case 32: { /* color3usv */
+    if (!qegl_gl_avail[32]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor3usv((const GLushort *) a0);
+    return 0;
+}
+case 33: { /* color4b */
+    if (!qegl_gl_avail[33]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4b(a0, a1, a2, a3);
+    return 0;
+}
+case 34: { /* color4bv */
+    if (!qegl_gl_avail[34]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4bv((const GLbyte *) a0);
+    return 0;
+}
+case 35: { /* color4d */
+    if (!qegl_gl_avail[35]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4d(a0, a1, a2, a3);
+    return 0;
+}
+case 36: { /* color4dv */
+    if (!qegl_gl_avail[36]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4dv((const GLdouble *) a0);
+    return 0;
+}
+case 37: { /* color4f */
+    if (!qegl_gl_avail[37]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4f(a0, a1, a2, a3);
+    return 0;
+}
+case 38: { /* color4fv */
+    if (!qegl_gl_avail[38]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4fv((const GLfloat *) a0);
+    return 0;
+}
+case 39: { /* color4i */
+    if (!qegl_gl_avail[39]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4i(a0, a1, a2, a3);
+    return 0;
+}
+case 40: { /* color4iv */
+    if (!qegl_gl_avail[40]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4iv((const GLint *) a0);
+    return 0;
+}
+case 41: { /* color4s */
+    if (!qegl_gl_avail[41]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4s(a0, a1, a2, a3);
+    return 0;
+}
+case 42: { /* color4sv */
+    if (!qegl_gl_avail[42]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4sv((const GLshort *) a0);
+    return 0;
+}
+case 43: { /* color4ub */
+    if (!qegl_gl_avail[43]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4ub(a0, a1, a2, a3);
+    return 0;
+}
+case 44: { /* color4ubv */
+    if (!qegl_gl_avail[44]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4ubv((const GLubyte *) a0);
+    return 0;
+}
+case 45: { /* color4ui */
+    if (!qegl_gl_avail[45]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4ui(a0, a1, a2, a3);
+    return 0;
+}
+case 46: { /* color4uiv */
+    if (!qegl_gl_avail[46]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4uiv((const GLuint *) a0);
+    return 0;
+}
+case 47: { /* color4us */
+    if (!qegl_gl_avail[47]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4us(a0, a1, a2, a3);
+    return 0;
+}
+case 48: { /* color4usv */
+    if (!qegl_gl_avail[48]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColor4usv((const GLushort *) a0);
+    return 0;
+}
+case 49: { /* color_mask */
+    if (!qegl_gl_avail[49]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColorMask(a0, a1, a2, a3);
+    return 0;
+}
+case 50: { /* color_material */
+    if (!qegl_gl_avail[50]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColorMaterial(a0, a1);
+    return 0;
+}
+case 52: { /* copy_pixels */
+    if (!qegl_gl_avail[52]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    uint32_t a4 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyPixels(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 53: { /* copy_tex_image1D */
+    if (!qegl_gl_avail[53]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    int32_t a6 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyTexImage1D(a0, a1, a2, a3, a4, a5, a6);
+    return 0;
+}
+case 54: { /* copy_tex_image2D */
+    if (!qegl_gl_avail[54]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    int32_t a6 = (int32_t) rd_u32(&r);
+    int32_t a7 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyTexImage2D(a0, a1, a2, a3, a4, a5, a6, a7);
+    return 0;
+}
+case 55: { /* copy_tex_sub_image1D */
+    if (!qegl_gl_avail[55]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyTexSubImage1D(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 56: { /* copy_tex_sub_image2D */
+    if (!qegl_gl_avail[56]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    int32_t a6 = (int32_t) rd_u32(&r);
+    int32_t a7 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyTexSubImage2D(a0, a1, a2, a3, a4, a5, a6, a7);
+    return 0;
+}
+case 57: { /* cull_face */
+    if (!qegl_gl_avail[57]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCullFace(a0);
+    return 0;
+}
+case 58: { /* delete_lists */
+    if (!qegl_gl_avail[58]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteLists(a0, a1);
+    return 0;
+}
+case 59: { /* delete_textures */
+    if (!qegl_gl_avail[59]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteTextures(a0, (const GLuint *) a1);
+    return 0;
+}
+case 60: { /* depth_func */
+    if (!qegl_gl_avail[60]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDepthFunc(a0);
+    return 0;
+}
+case 61: { /* depth_mask */
+    if (!qegl_gl_avail[61]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDepthMask(a0);
+    return 0;
+}
+case 62: { /* depth_range */
+    if (!qegl_gl_avail[62]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDepthRange(a0, a1);
+    return 0;
+}
+case 63: { /* disable */
+    if (!qegl_gl_avail[63]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDisable(a0);
+    return 0;
+}
+case 64: { /* disable_client_state */
+    if (!qegl_gl_avail[64]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDisableClientState(a0);
+    return 0;
+}
+case 69: { /* edge_flag */
+    if (!qegl_gl_avail[69]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEdgeFlag(a0);
+    return 0;
+}
+case 71: { /* edge_flagv */
+    if (!qegl_gl_avail[71]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEdgeFlagv((const GLboolean *) a0);
+    return 0;
+}
+case 72: { /* enable */
+    if (!qegl_gl_avail[72]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEnable(a0);
+    return 0;
+}
+case 73: { /* enable_client_state */
+    if (!qegl_gl_avail[73]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEnableClientState(a0);
+    return 0;
+}
+case 74: { /* end */
+    if (!qegl_gl_avail[74]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEnd();
+    return 0;
+}
+case 75: { /* end_list */
+    if (!qegl_gl_avail[75]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEndList();
+    return 0;
+}
+case 76: { /* eval_coord1d */
+    if (!qegl_gl_avail[76]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord1d(a0);
+    return 0;
+}
+case 77: { /* eval_coord1dv */
+    if (!qegl_gl_avail[77]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord1dv((const GLdouble *) a0);
+    return 0;
+}
+case 78: { /* eval_coord1f */
+    if (!qegl_gl_avail[78]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord1f(a0);
+    return 0;
+}
+case 79: { /* eval_coord1fv */
+    if (!qegl_gl_avail[79]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord1fv((const GLfloat *) a0);
+    return 0;
+}
+case 80: { /* eval_coord2d */
+    if (!qegl_gl_avail[80]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord2d(a0, a1);
+    return 0;
+}
+case 81: { /* eval_coord2dv */
+    if (!qegl_gl_avail[81]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord2dv((const GLdouble *) a0);
+    return 0;
+}
+case 82: { /* eval_coord2f */
+    if (!qegl_gl_avail[82]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord2f(a0, a1);
+    return 0;
+}
+case 83: { /* eval_coord2fv */
+    if (!qegl_gl_avail[83]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalCoord2fv((const GLfloat *) a0);
+    return 0;
+}
+case 84: { /* eval_mesh1 */
+    if (!qegl_gl_avail[84]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalMesh1(a0, a1, a2);
+    return 0;
+}
+case 85: { /* eval_mesh2 */
+    if (!qegl_gl_avail[85]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalMesh2(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 86: { /* eval_point1 */
+    if (!qegl_gl_avail[86]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalPoint1(a0);
+    return 0;
+}
+case 87: { /* eval_point2 */
+    if (!qegl_gl_avail[87]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEvalPoint2(a0, a1);
+    return 0;
+}
+case 89: { /* finish */
+    if (!qegl_gl_avail[89]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFinish();
+    return 0;
+}
+case 90: { /* flush */
+    if (!qegl_gl_avail[90]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFlush();
+    return 0;
+}
+case 91: { /* fogf */
+    if (!qegl_gl_avail[91]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogf(a0, a1);
+    return 0;
+}
+case 92: { /* fogfv */
+    if (!qegl_gl_avail[92]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogfv(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 93: { /* fogi */
+    if (!qegl_gl_avail[93]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogi(a0, a1);
+    return 0;
+}
+case 94: { /* fogiv */
+    if (!qegl_gl_avail[94]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogiv(a0, (const GLint *) a1);
+    return 0;
+}
+case 95: { /* front_face */
+    if (!qegl_gl_avail[95]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFrontFace(a0);
+    return 0;
+}
+case 96: { /* frustum */
+    if (!qegl_gl_avail[96]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    double a4 = rd_f64(&r);
+    double a5 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFrustum(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 99: { /* get_booleanv */
+    if (!qegl_gl_avail[99]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *a1 = rd_out(&r, 16);
+    uint8_t t1[4096];
+    memset(t1, 0, sizeof(t1));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetBooleanv(a0, (GLboolean *) t1);
+    memcpy(a1, t1, 16);
+    return 0;
+}
+case 100: { /* get_clip_plane */
+    if (!qegl_gl_avail[100]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *a1 = rd_out(&r, 128);
+    uint8_t t1[4096];
+    memset(t1, 0, sizeof(t1));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetClipPlane(a0, (GLdouble *) t1);
+    memcpy(a1, t1, 128);
+    return 0;
+}
+case 101: { /* get_doublev */
+    if (!qegl_gl_avail[101]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *a1 = rd_out(&r, 128);
+    uint8_t t1[4096];
+    memset(t1, 0, sizeof(t1));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetDoublev(a0, (GLdouble *) t1);
+    memcpy(a1, t1, 128);
+    return 0;
+}
+case 102: { /* get_error */
+    if (!qegl_gl_avail[102]) return QEGL_ERR_GL;
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glGetError();
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 103: { /* get_floatv */
+    if (!qegl_gl_avail[103]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *a1 = rd_out(&r, 64);
+    uint8_t t1[4096];
+    memset(t1, 0, sizeof(t1));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetFloatv(a0, (GLfloat *) t1);
+    memcpy(a1, t1, 64);
+    return 0;
+}
+case 104: { /* get_integerv */
+    if (!qegl_gl_avail[104]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *a1 = rd_out(&r, 64);
+    uint8_t t1[4096];
+    memset(t1, 0, sizeof(t1));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetIntegerv(a0, (GLint *) t1);
+    memcpy(a1, t1, 64);
+    return 0;
+}
+case 105: { /* get_lightfv */
+    if (!qegl_gl_avail[105]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetLightfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 106: { /* get_lightiv */
+    if (!qegl_gl_avail[106]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetLightiv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 110: { /* get_materialfv */
+    if (!qegl_gl_avail[110]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetMaterialfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 111: { /* get_materialiv */
+    if (!qegl_gl_avail[111]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetMaterialiv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 118: { /* get_tex_envfv */
+    if (!qegl_gl_avail[118]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexEnvfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 119: { /* get_tex_enviv */
+    if (!qegl_gl_avail[119]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexEnviv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 120: { /* get_tex_gendv */
+    if (!qegl_gl_avail[120]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexGendv(a0, a1, (GLdouble *) t2);
+    memcpy(a2, t2, 128);
+    return 0;
+}
+case 121: { /* get_tex_genfv */
+    if (!qegl_gl_avail[121]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexGenfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 122: { /* get_tex_geniv */
+    if (!qegl_gl_avail[122]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexGeniv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 124: { /* get_tex_level_parameterfv */
+    if (!qegl_gl_avail[124]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    void *a3 = rd_out(&r, 64);
+    uint8_t t3[4096];
+    memset(t3, 0, sizeof(t3));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexLevelParameterfv(a0, a1, a2, (GLfloat *) t3);
+    memcpy(a3, t3, 64);
+    return 0;
+}
+case 125: { /* get_tex_level_parameteriv */
+    if (!qegl_gl_avail[125]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    void *a3 = rd_out(&r, 64);
+    uint8_t t3[4096];
+    memset(t3, 0, sizeof(t3));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexLevelParameteriv(a0, a1, a2, (GLint *) t3);
+    memcpy(a3, t3, 64);
+    return 0;
+}
+case 126: { /* get_tex_parameterfv */
+    if (!qegl_gl_avail[126]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexParameterfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 127: { /* get_tex_parameteriv */
+    if (!qegl_gl_avail[127]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetTexParameteriv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 128: { /* hint */
+    if (!qegl_gl_avail[128]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glHint(a0, a1);
+    return 0;
+}
+case 129: { /* index_mask */
+    if (!qegl_gl_avail[129]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexMask(a0);
+    return 0;
+}
+case 131: { /* indexd */
+    if (!qegl_gl_avail[131]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexd(a0);
+    return 0;
+}
+case 132: { /* indexdv */
+    if (!qegl_gl_avail[132]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexdv((const GLdouble *) a0);
+    return 0;
+}
+case 133: { /* indexf */
+    if (!qegl_gl_avail[133]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexf(a0);
+    return 0;
+}
+case 134: { /* indexfv */
+    if (!qegl_gl_avail[134]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexfv((const GLfloat *) a0);
+    return 0;
+}
+case 135: { /* indexi */
+    if (!qegl_gl_avail[135]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexi(a0);
+    return 0;
+}
+case 136: { /* indexiv */
+    if (!qegl_gl_avail[136]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexiv((const GLint *) a0);
+    return 0;
+}
+case 137: { /* indexs */
+    if (!qegl_gl_avail[137]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexs(a0);
+    return 0;
+}
+case 138: { /* indexsv */
+    if (!qegl_gl_avail[138]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexsv((const GLshort *) a0);
+    return 0;
+}
+case 139: { /* indexub */
+    if (!qegl_gl_avail[139]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexub(a0);
+    return 0;
+}
+case 140: { /* indexubv */
+    if (!qegl_gl_avail[140]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glIndexubv((const GLubyte *) a0);
+    return 0;
+}
+case 141: { /* init_names */
+    if (!qegl_gl_avail[141]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glInitNames();
+    return 0;
+}
+case 143: { /* is_enabled */
+    if (!qegl_gl_avail[143]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsEnabled(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 144: { /* is_list */
+    if (!qegl_gl_avail[144]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsList(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 145: { /* is_texture */
+    if (!qegl_gl_avail[145]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsTexture(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 146: { /* light_modelf */
+    if (!qegl_gl_avail[146]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLightModelf(a0, a1);
+    return 0;
+}
+case 147: { /* light_modelfv */
+    if (!qegl_gl_avail[147]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLightModelfv(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 148: { /* light_modeli */
+    if (!qegl_gl_avail[148]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLightModeli(a0, a1);
+    return 0;
+}
+case 149: { /* light_modeliv */
+    if (!qegl_gl_avail[149]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLightModeliv(a0, (const GLint *) a1);
+    return 0;
+}
+case 150: { /* lightf */
+    if (!qegl_gl_avail[150]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLightf(a0, a1, a2);
+    return 0;
+}
+case 151: { /* lightfv */
+    if (!qegl_gl_avail[151]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLightfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 152: { /* lighti */
+    if (!qegl_gl_avail[152]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLighti(a0, a1, a2);
+    return 0;
+}
+case 153: { /* lightiv */
+    if (!qegl_gl_avail[153]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLightiv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 154: { /* line_stipple */
+    if (!qegl_gl_avail[154]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLineStipple(a0, a1);
+    return 0;
+}
+case 155: { /* line_width */
+    if (!qegl_gl_avail[155]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLineWidth(a0);
+    return 0;
+}
+case 156: { /* list_base */
+    if (!qegl_gl_avail[156]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glListBase(a0);
+    return 0;
+}
+case 157: { /* load_identity */
+    if (!qegl_gl_avail[157]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLoadIdentity();
+    return 0;
+}
+case 158: { /* load_matrixd */
+    if (!qegl_gl_avail[158]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLoadMatrixd((const GLdouble *) a0);
+    return 0;
+}
+case 159: { /* load_matrixf */
+    if (!qegl_gl_avail[159]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLoadMatrixf((const GLfloat *) a0);
+    return 0;
+}
+case 160: { /* load_name */
+    if (!qegl_gl_avail[160]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLoadName(a0);
+    return 0;
+}
+case 161: { /* logic_op */
+    if (!qegl_gl_avail[161]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLogicOp(a0);
+    return 0;
+}
+case 166: { /* map_grid1d */
+    if (!qegl_gl_avail[166]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMapGrid1d(a0, a1, a2);
+    return 0;
+}
+case 167: { /* map_grid1f */
+    if (!qegl_gl_avail[167]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMapGrid1f(a0, a1, a2);
+    return 0;
+}
+case 168: { /* map_grid2d */
+    if (!qegl_gl_avail[168]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    double a4 = rd_f64(&r);
+    double a5 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMapGrid2d(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 169: { /* map_grid2f */
+    if (!qegl_gl_avail[169]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    float a4 = rd_f32(&r);
+    float a5 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMapGrid2f(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 170: { /* materialf */
+    if (!qegl_gl_avail[170]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMaterialf(a0, a1, a2);
+    return 0;
+}
+case 171: { /* materialfv */
+    if (!qegl_gl_avail[171]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMaterialfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 172: { /* materiali */
+    if (!qegl_gl_avail[172]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMateriali(a0, a1, a2);
+    return 0;
+}
+case 173: { /* materialiv */
+    if (!qegl_gl_avail[173]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMaterialiv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 174: { /* matrix_mode */
+    if (!qegl_gl_avail[174]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMatrixMode(a0);
+    return 0;
+}
+case 175: { /* mult_matrixd */
+    if (!qegl_gl_avail[175]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultMatrixd((const GLdouble *) a0);
+    return 0;
+}
+case 176: { /* mult_matrixf */
+    if (!qegl_gl_avail[176]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultMatrixf((const GLfloat *) a0);
+    return 0;
+}
+case 177: { /* new_list */
+    if (!qegl_gl_avail[177]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNewList(a0, a1);
+    return 0;
+}
+case 178: { /* normal3b */
+    if (!qegl_gl_avail[178]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3b(a0, a1, a2);
+    return 0;
+}
+case 179: { /* normal3bv */
+    if (!qegl_gl_avail[179]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3bv((const GLbyte *) a0);
+    return 0;
+}
+case 180: { /* normal3d */
+    if (!qegl_gl_avail[180]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3d(a0, a1, a2);
+    return 0;
+}
+case 181: { /* normal3dv */
+    if (!qegl_gl_avail[181]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3dv((const GLdouble *) a0);
+    return 0;
+}
+case 182: { /* normal3f */
+    if (!qegl_gl_avail[182]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3f(a0, a1, a2);
+    return 0;
+}
+case 183: { /* normal3fv */
+    if (!qegl_gl_avail[183]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3fv((const GLfloat *) a0);
+    return 0;
+}
+case 184: { /* normal3i */
+    if (!qegl_gl_avail[184]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3i(a0, a1, a2);
+    return 0;
+}
+case 185: { /* normal3iv */
+    if (!qegl_gl_avail[185]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3iv((const GLint *) a0);
+    return 0;
+}
+case 186: { /* normal3s */
+    if (!qegl_gl_avail[186]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3s(a0, a1, a2);
+    return 0;
+}
+case 187: { /* normal3sv */
+    if (!qegl_gl_avail[187]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glNormal3sv((const GLshort *) a0);
+    return 0;
+}
+case 189: { /* ortho */
+    if (!qegl_gl_avail[189]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    double a4 = rd_f64(&r);
+    double a5 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glOrtho(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 190: { /* pass_through */
+    if (!qegl_gl_avail[190]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPassThrough(a0);
+    return 0;
+}
+case 196: { /* pixel_transferf */
+    if (!qegl_gl_avail[196]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPixelTransferf(a0, a1);
+    return 0;
+}
+case 197: { /* pixel_transferi */
+    if (!qegl_gl_avail[197]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPixelTransferi(a0, a1);
+    return 0;
+}
+case 198: { /* pixel_zoom */
+    if (!qegl_gl_avail[198]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPixelZoom(a0, a1);
+    return 0;
+}
+case 199: { /* point_size */
+    if (!qegl_gl_avail[199]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPointSize(a0);
+    return 0;
+}
+case 200: { /* polygon_mode */
+    if (!qegl_gl_avail[200]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPolygonMode(a0, a1);
+    return 0;
+}
+case 201: { /* polygon_offset */
+    if (!qegl_gl_avail[201]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPolygonOffset(a0, a1);
+    return 0;
+}
+case 203: { /* pop_attrib */
+    if (!qegl_gl_avail[203]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPopAttrib();
+    return 0;
+}
+case 205: { /* pop_matrix */
+    if (!qegl_gl_avail[205]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPopMatrix();
+    return 0;
+}
+case 206: { /* pop_name */
+    if (!qegl_gl_avail[206]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPopName();
+    return 0;
+}
+case 207: { /* prioritize_textures */
+    if (!qegl_gl_avail[207]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPrioritizeTextures(a0, (const GLuint *) a1, (const GLclampf *) a2);
+    return 0;
+}
+case 208: { /* push_attrib */
+    if (!qegl_gl_avail[208]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPushAttrib(a0);
+    return 0;
+}
+case 210: { /* push_matrix */
+    if (!qegl_gl_avail[210]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPushMatrix();
+    return 0;
+}
+case 211: { /* push_name */
+    if (!qegl_gl_avail[211]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPushName(a0);
+    return 0;
+}
+case 212: { /* raster_pos2d */
+    if (!qegl_gl_avail[212]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2d(a0, a1);
+    return 0;
+}
+case 213: { /* raster_pos2dv */
+    if (!qegl_gl_avail[213]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2dv((const GLdouble *) a0);
+    return 0;
+}
+case 214: { /* raster_pos2f */
+    if (!qegl_gl_avail[214]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2f(a0, a1);
+    return 0;
+}
+case 215: { /* raster_pos2fv */
+    if (!qegl_gl_avail[215]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2fv((const GLfloat *) a0);
+    return 0;
+}
+case 216: { /* raster_pos2i */
+    if (!qegl_gl_avail[216]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2i(a0, a1);
+    return 0;
+}
+case 217: { /* raster_pos2iv */
+    if (!qegl_gl_avail[217]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2iv((const GLint *) a0);
+    return 0;
+}
+case 218: { /* raster_pos2s */
+    if (!qegl_gl_avail[218]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2s(a0, a1);
+    return 0;
+}
+case 219: { /* raster_pos2sv */
+    if (!qegl_gl_avail[219]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos2sv((const GLshort *) a0);
+    return 0;
+}
+case 220: { /* raster_pos3d */
+    if (!qegl_gl_avail[220]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3d(a0, a1, a2);
+    return 0;
+}
+case 221: { /* raster_pos3dv */
+    if (!qegl_gl_avail[221]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3dv((const GLdouble *) a0);
+    return 0;
+}
+case 222: { /* raster_pos3f */
+    if (!qegl_gl_avail[222]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3f(a0, a1, a2);
+    return 0;
+}
+case 223: { /* raster_pos3fv */
+    if (!qegl_gl_avail[223]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3fv((const GLfloat *) a0);
+    return 0;
+}
+case 224: { /* raster_pos3i */
+    if (!qegl_gl_avail[224]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3i(a0, a1, a2);
+    return 0;
+}
+case 225: { /* raster_pos3iv */
+    if (!qegl_gl_avail[225]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3iv((const GLint *) a0);
+    return 0;
+}
+case 226: { /* raster_pos3s */
+    if (!qegl_gl_avail[226]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3s(a0, a1, a2);
+    return 0;
+}
+case 227: { /* raster_pos3sv */
+    if (!qegl_gl_avail[227]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos3sv((const GLshort *) a0);
+    return 0;
+}
+case 228: { /* raster_pos4d */
+    if (!qegl_gl_avail[228]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4d(a0, a1, a2, a3);
+    return 0;
+}
+case 229: { /* raster_pos4dv */
+    if (!qegl_gl_avail[229]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4dv((const GLdouble *) a0);
+    return 0;
+}
+case 230: { /* raster_pos4f */
+    if (!qegl_gl_avail[230]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4f(a0, a1, a2, a3);
+    return 0;
+}
+case 231: { /* raster_pos4fv */
+    if (!qegl_gl_avail[231]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4fv((const GLfloat *) a0);
+    return 0;
+}
+case 232: { /* raster_pos4i */
+    if (!qegl_gl_avail[232]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4i(a0, a1, a2, a3);
+    return 0;
+}
+case 233: { /* raster_pos4iv */
+    if (!qegl_gl_avail[233]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4iv((const GLint *) a0);
+    return 0;
+}
+case 234: { /* raster_pos4s */
+    if (!qegl_gl_avail[234]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4s(a0, a1, a2, a3);
+    return 0;
+}
+case 235: { /* raster_pos4sv */
+    if (!qegl_gl_avail[235]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRasterPos4sv((const GLshort *) a0);
+    return 0;
+}
+case 238: { /* rectd */
+    if (!qegl_gl_avail[238]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRectd(a0, a1, a2, a3);
+    return 0;
+}
+case 239: { /* rectdv */
+    if (!qegl_gl_avail[239]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRectdv((const GLdouble *) a0, (const GLdouble *) a1);
+    return 0;
+}
+case 240: { /* rectf */
+    if (!qegl_gl_avail[240]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRectf(a0, a1, a2, a3);
+    return 0;
+}
+case 241: { /* rectfv */
+    if (!qegl_gl_avail[241]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRectfv((const GLfloat *) a0, (const GLfloat *) a1);
+    return 0;
+}
+case 242: { /* recti */
+    if (!qegl_gl_avail[242]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRecti(a0, a1, a2, a3);
+    return 0;
+}
+case 243: { /* rectiv */
+    if (!qegl_gl_avail[243]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRectiv((const GLint *) a0, (const GLint *) a1);
+    return 0;
+}
+case 244: { /* rects */
+    if (!qegl_gl_avail[244]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRects(a0, a1, a2, a3);
+    return 0;
+}
+case 245: { /* rectsv */
+    if (!qegl_gl_avail[245]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRectsv((const GLshort *) a0, (const GLshort *) a1);
+    return 0;
+}
+case 246: { /* render_mode */
+    if (!qegl_gl_avail[246]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glRenderMode(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 247: { /* rotated */
+    if (!qegl_gl_avail[247]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRotated(a0, a1, a2, a3);
+    return 0;
+}
+case 248: { /* rotatef */
+    if (!qegl_gl_avail[248]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRotatef(a0, a1, a2, a3);
+    return 0;
+}
+case 249: { /* scaled */
+    if (!qegl_gl_avail[249]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glScaled(a0, a1, a2);
+    return 0;
+}
+case 250: { /* scalef */
+    if (!qegl_gl_avail[250]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glScalef(a0, a1, a2);
+    return 0;
+}
+case 251: { /* scissor */
+    if (!qegl_gl_avail[251]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glScissor(a0, a1, a2, a3);
+    return 0;
+}
+case 253: { /* shade_model */
+    if (!qegl_gl_avail[253]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glShadeModel(a0);
+    return 0;
+}
+case 254: { /* stencil_func */
+    if (!qegl_gl_avail[254]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glStencilFunc(a0, a1, a2);
+    return 0;
+}
+case 255: { /* stencil_mask */
+    if (!qegl_gl_avail[255]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glStencilMask(a0);
+    return 0;
+}
+case 256: { /* stencil_op */
+    if (!qegl_gl_avail[256]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glStencilOp(a0, a1, a2);
+    return 0;
+}
+case 257: { /* tex_coord1d */
+    if (!qegl_gl_avail[257]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1d(a0);
+    return 0;
+}
+case 258: { /* tex_coord1dv */
+    if (!qegl_gl_avail[258]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1dv((const GLdouble *) a0);
+    return 0;
+}
+case 259: { /* tex_coord1f */
+    if (!qegl_gl_avail[259]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1f(a0);
+    return 0;
+}
+case 260: { /* tex_coord1fv */
+    if (!qegl_gl_avail[260]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1fv((const GLfloat *) a0);
+    return 0;
+}
+case 261: { /* tex_coord1i */
+    if (!qegl_gl_avail[261]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1i(a0);
+    return 0;
+}
+case 262: { /* tex_coord1iv */
+    if (!qegl_gl_avail[262]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1iv((const GLint *) a0);
+    return 0;
+}
+case 263: { /* tex_coord1s */
+    if (!qegl_gl_avail[263]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1s(a0);
+    return 0;
+}
+case 264: { /* tex_coord1sv */
+    if (!qegl_gl_avail[264]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord1sv((const GLshort *) a0);
+    return 0;
+}
+case 265: { /* tex_coord2d */
+    if (!qegl_gl_avail[265]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2d(a0, a1);
+    return 0;
+}
+case 266: { /* tex_coord2dv */
+    if (!qegl_gl_avail[266]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2dv((const GLdouble *) a0);
+    return 0;
+}
+case 267: { /* tex_coord2f */
+    if (!qegl_gl_avail[267]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2f(a0, a1);
+    return 0;
+}
+case 268: { /* tex_coord2fv */
+    if (!qegl_gl_avail[268]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2fv((const GLfloat *) a0);
+    return 0;
+}
+case 269: { /* tex_coord2i */
+    if (!qegl_gl_avail[269]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2i(a0, a1);
+    return 0;
+}
+case 270: { /* tex_coord2iv */
+    if (!qegl_gl_avail[270]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2iv((const GLint *) a0);
+    return 0;
+}
+case 271: { /* tex_coord2s */
+    if (!qegl_gl_avail[271]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2s(a0, a1);
+    return 0;
+}
+case 272: { /* tex_coord2sv */
+    if (!qegl_gl_avail[272]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord2sv((const GLshort *) a0);
+    return 0;
+}
+case 273: { /* tex_coord3d */
+    if (!qegl_gl_avail[273]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3d(a0, a1, a2);
+    return 0;
+}
+case 274: { /* tex_coord3dv */
+    if (!qegl_gl_avail[274]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3dv((const GLdouble *) a0);
+    return 0;
+}
+case 275: { /* tex_coord3f */
+    if (!qegl_gl_avail[275]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3f(a0, a1, a2);
+    return 0;
+}
+case 276: { /* tex_coord3fv */
+    if (!qegl_gl_avail[276]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3fv((const GLfloat *) a0);
+    return 0;
+}
+case 277: { /* tex_coord3i */
+    if (!qegl_gl_avail[277]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3i(a0, a1, a2);
+    return 0;
+}
+case 278: { /* tex_coord3iv */
+    if (!qegl_gl_avail[278]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3iv((const GLint *) a0);
+    return 0;
+}
+case 279: { /* tex_coord3s */
+    if (!qegl_gl_avail[279]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3s(a0, a1, a2);
+    return 0;
+}
+case 280: { /* tex_coord3sv */
+    if (!qegl_gl_avail[280]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord3sv((const GLshort *) a0);
+    return 0;
+}
+case 281: { /* tex_coord4d */
+    if (!qegl_gl_avail[281]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4d(a0, a1, a2, a3);
+    return 0;
+}
+case 282: { /* tex_coord4dv */
+    if (!qegl_gl_avail[282]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4dv((const GLdouble *) a0);
+    return 0;
+}
+case 283: { /* tex_coord4f */
+    if (!qegl_gl_avail[283]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4f(a0, a1, a2, a3);
+    return 0;
+}
+case 284: { /* tex_coord4fv */
+    if (!qegl_gl_avail[284]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4fv((const GLfloat *) a0);
+    return 0;
+}
+case 285: { /* tex_coord4i */
+    if (!qegl_gl_avail[285]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4i(a0, a1, a2, a3);
+    return 0;
+}
+case 286: { /* tex_coord4iv */
+    if (!qegl_gl_avail[286]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4iv((const GLint *) a0);
+    return 0;
+}
+case 287: { /* tex_coord4s */
+    if (!qegl_gl_avail[287]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4s(a0, a1, a2, a3);
+    return 0;
+}
+case 288: { /* tex_coord4sv */
+    if (!qegl_gl_avail[288]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexCoord4sv((const GLshort *) a0);
+    return 0;
+}
+case 290: { /* tex_envf */
+    if (!qegl_gl_avail[290]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexEnvf(a0, a1, a2);
+    return 0;
+}
+case 291: { /* tex_envfv */
+    if (!qegl_gl_avail[291]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexEnvfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 292: { /* tex_envi */
+    if (!qegl_gl_avail[292]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexEnvi(a0, a1, a2);
+    return 0;
+}
+case 293: { /* tex_enviv */
+    if (!qegl_gl_avail[293]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexEnviv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 294: { /* tex_gend */
+    if (!qegl_gl_avail[294]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexGend(a0, a1, a2);
+    return 0;
+}
+case 295: { /* tex_gendv */
+    if (!qegl_gl_avail[295]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexGendv(a0, a1, (const GLdouble *) a2);
+    return 0;
+}
+case 296: { /* tex_genf */
+    if (!qegl_gl_avail[296]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexGenf(a0, a1, a2);
+    return 0;
+}
+case 297: { /* tex_genfv */
+    if (!qegl_gl_avail[297]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexGenfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 298: { /* tex_geni */
+    if (!qegl_gl_avail[298]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexGeni(a0, a1, a2);
+    return 0;
+}
+case 299: { /* tex_geniv */
+    if (!qegl_gl_avail[299]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexGeniv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 302: { /* tex_parameterf */
+    if (!qegl_gl_avail[302]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexParameterf(a0, a1, a2);
+    return 0;
+}
+case 303: { /* tex_parameterfv */
+    if (!qegl_gl_avail[303]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexParameterfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 304: { /* tex_parameteri */
+    if (!qegl_gl_avail[304]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexParameteri(a0, a1, a2);
+    return 0;
+}
+case 305: { /* tex_parameteriv */
+    if (!qegl_gl_avail[305]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexParameteriv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 308: { /* translated */
+    if (!qegl_gl_avail[308]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTranslated(a0, a1, a2);
+    return 0;
+}
+case 309: { /* translatef */
+    if (!qegl_gl_avail[309]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTranslatef(a0, a1, a2);
+    return 0;
+}
+case 310: { /* vertex2d */
+    if (!qegl_gl_avail[310]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2d(a0, a1);
+    return 0;
+}
+case 311: { /* vertex2dv */
+    if (!qegl_gl_avail[311]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2dv((const GLdouble *) a0);
+    return 0;
+}
+case 312: { /* vertex2f */
+    if (!qegl_gl_avail[312]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2f(a0, a1);
+    return 0;
+}
+case 313: { /* vertex2fv */
+    if (!qegl_gl_avail[313]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2fv((const GLfloat *) a0);
+    return 0;
+}
+case 314: { /* vertex2i */
+    if (!qegl_gl_avail[314]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2i(a0, a1);
+    return 0;
+}
+case 315: { /* vertex2iv */
+    if (!qegl_gl_avail[315]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2iv((const GLint *) a0);
+    return 0;
+}
+case 316: { /* vertex2s */
+    if (!qegl_gl_avail[316]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2s(a0, a1);
+    return 0;
+}
+case 317: { /* vertex2sv */
+    if (!qegl_gl_avail[317]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex2sv((const GLshort *) a0);
+    return 0;
+}
+case 318: { /* vertex3d */
+    if (!qegl_gl_avail[318]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3d(a0, a1, a2);
+    return 0;
+}
+case 319: { /* vertex3dv */
+    if (!qegl_gl_avail[319]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3dv((const GLdouble *) a0);
+    return 0;
+}
+case 320: { /* vertex3f */
+    if (!qegl_gl_avail[320]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3f(a0, a1, a2);
+    return 0;
+}
+case 321: { /* vertex3fv */
+    if (!qegl_gl_avail[321]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3fv((const GLfloat *) a0);
+    return 0;
+}
+case 322: { /* vertex3i */
+    if (!qegl_gl_avail[322]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3i(a0, a1, a2);
+    return 0;
+}
+case 323: { /* vertex3iv */
+    if (!qegl_gl_avail[323]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3iv((const GLint *) a0);
+    return 0;
+}
+case 324: { /* vertex3s */
+    if (!qegl_gl_avail[324]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3s(a0, a1, a2);
+    return 0;
+}
+case 325: { /* vertex3sv */
+    if (!qegl_gl_avail[325]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex3sv((const GLshort *) a0);
+    return 0;
+}
+case 326: { /* vertex4d */
+    if (!qegl_gl_avail[326]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4d(a0, a1, a2, a3);
+    return 0;
+}
+case 327: { /* vertex4dv */
+    if (!qegl_gl_avail[327]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4dv((const GLdouble *) a0);
+    return 0;
+}
+case 328: { /* vertex4f */
+    if (!qegl_gl_avail[328]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4f(a0, a1, a2, a3);
+    return 0;
+}
+case 329: { /* vertex4fv */
+    if (!qegl_gl_avail[329]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4fv((const GLfloat *) a0);
+    return 0;
+}
+case 330: { /* vertex4i */
+    if (!qegl_gl_avail[330]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4i(a0, a1, a2, a3);
+    return 0;
+}
+case 331: { /* vertex4iv */
+    if (!qegl_gl_avail[331]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4iv((const GLint *) a0);
+    return 0;
+}
+case 332: { /* vertex4s */
+    if (!qegl_gl_avail[332]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4s(a0, a1, a2, a3);
+    return 0;
+}
+case 333: { /* vertex4sv */
+    if (!qegl_gl_avail[333]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertex4sv((const GLshort *) a0);
+    return 0;
+}
+case 335: { /* viewport */
+    if (!qegl_gl_avail[335]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glViewport(a0, a1, a2, a3);
+    return 0;
+}
+case 336: { /* blend_func_separate */
+    if (!qegl_gl_avail[336]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBlendFuncSeparate(a0, a1, a2, a3);
+    return 0;
+}
+case 337: { /* blend_color */
+    if (!qegl_gl_avail[337]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBlendColor(a0, a1, a2, a3);
+    return 0;
+}
+case 338: { /* blend_equation */
+    if (!qegl_gl_avail[338]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBlendEquation(a0);
+    return 0;
+}
+case 341: { /* client_active_texture */
+    if (!qegl_gl_avail[341]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClientActiveTexture(a0);
+    return 0;
+}
+case 342: { /* active_texture */
+    if (!qegl_gl_avail[342]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glActiveTexture(a0);
+    return 0;
+}
+case 343: { /* multi_tex_coord1d */
+    if (!qegl_gl_avail[343]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1d(a0, a1);
+    return 0;
+}
+case 344: { /* multi_tex_coord1dv */
+    if (!qegl_gl_avail[344]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1dv(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 345: { /* multi_tex_coord1f */
+    if (!qegl_gl_avail[345]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1f(a0, a1);
+    return 0;
+}
+case 346: { /* multi_tex_coord1fv */
+    if (!qegl_gl_avail[346]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1fv(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 347: { /* multi_tex_coord1i */
+    if (!qegl_gl_avail[347]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1i(a0, a1);
+    return 0;
+}
+case 348: { /* multi_tex_coord1iv */
+    if (!qegl_gl_avail[348]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1iv(a0, (const GLint *) a1);
+    return 0;
+}
+case 349: { /* multi_tex_coord1s */
+    if (!qegl_gl_avail[349]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1s(a0, a1);
+    return 0;
+}
+case 350: { /* multi_tex_coord1sv */
+    if (!qegl_gl_avail[350]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord1sv(a0, (const GLshort *) a1);
+    return 0;
+}
+case 351: { /* multi_tex_coord2d */
+    if (!qegl_gl_avail[351]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2d(a0, a1, a2);
+    return 0;
+}
+case 352: { /* multi_tex_coord2dv */
+    if (!qegl_gl_avail[352]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2dv(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 353: { /* multi_tex_coord2f */
+    if (!qegl_gl_avail[353]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2f(a0, a1, a2);
+    return 0;
+}
+case 354: { /* multi_tex_coord2fv */
+    if (!qegl_gl_avail[354]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2fv(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 355: { /* multi_tex_coord2i */
+    if (!qegl_gl_avail[355]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2i(a0, a1, a2);
+    return 0;
+}
+case 356: { /* multi_tex_coord2iv */
+    if (!qegl_gl_avail[356]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2iv(a0, (const GLint *) a1);
+    return 0;
+}
+case 357: { /* multi_tex_coord2s */
+    if (!qegl_gl_avail[357]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2s(a0, a1, a2);
+    return 0;
+}
+case 358: { /* multi_tex_coord2sv */
+    if (!qegl_gl_avail[358]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord2sv(a0, (const GLshort *) a1);
+    return 0;
+}
+case 359: { /* multi_tex_coord3d */
+    if (!qegl_gl_avail[359]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3d(a0, a1, a2, a3);
+    return 0;
+}
+case 360: { /* multi_tex_coord3dv */
+    if (!qegl_gl_avail[360]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3dv(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 361: { /* multi_tex_coord3f */
+    if (!qegl_gl_avail[361]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3f(a0, a1, a2, a3);
+    return 0;
+}
+case 362: { /* multi_tex_coord3fv */
+    if (!qegl_gl_avail[362]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3fv(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 363: { /* multi_tex_coord3i */
+    if (!qegl_gl_avail[363]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3i(a0, a1, a2, a3);
+    return 0;
+}
+case 364: { /* multi_tex_coord3iv */
+    if (!qegl_gl_avail[364]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3iv(a0, (const GLint *) a1);
+    return 0;
+}
+case 365: { /* multi_tex_coord3s */
+    if (!qegl_gl_avail[365]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3s(a0, a1, a2, a3);
+    return 0;
+}
+case 366: { /* multi_tex_coord3sv */
+    if (!qegl_gl_avail[366]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord3sv(a0, (const GLshort *) a1);
+    return 0;
+}
+case 367: { /* multi_tex_coord4d */
+    if (!qegl_gl_avail[367]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    double a4 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4d(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 368: { /* multi_tex_coord4dv */
+    if (!qegl_gl_avail[368]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4dv(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 369: { /* multi_tex_coord4f */
+    if (!qegl_gl_avail[369]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    float a4 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4f(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 370: { /* multi_tex_coord4fv */
+    if (!qegl_gl_avail[370]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4fv(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 371: { /* multi_tex_coord4i */
+    if (!qegl_gl_avail[371]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4i(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 372: { /* multi_tex_coord4iv */
+    if (!qegl_gl_avail[372]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4iv(a0, (const GLint *) a1);
+    return 0;
+}
+case 373: { /* multi_tex_coord4s */
+    if (!qegl_gl_avail[373]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4s(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 374: { /* multi_tex_coord4sv */
+    if (!qegl_gl_avail[374]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultiTexCoord4sv(a0, (const GLshort *) a1);
+    return 0;
+}
+case 375: { /* load_transpose_matrixd */
+    if (!qegl_gl_avail[375]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLoadTransposeMatrixd((const GLdouble *) a0);
+    return 0;
+}
+case 376: { /* load_transpose_matrixf */
+    if (!qegl_gl_avail[376]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLoadTransposeMatrixf((const GLfloat *) a0);
+    return 0;
+}
+case 377: { /* mult_transpose_matrixd */
+    if (!qegl_gl_avail[377]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultTransposeMatrixd((const GLdouble *) a0);
+    return 0;
+}
+case 378: { /* mult_transpose_matrixf */
+    if (!qegl_gl_avail[378]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMultTransposeMatrixf((const GLfloat *) a0);
+    return 0;
+}
+case 386: { /* secondary_color3b */
+    if (!qegl_gl_avail[386]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3b(a0, a1, a2);
+    return 0;
+}
+case 387: { /* secondary_color3bv */
+    if (!qegl_gl_avail[387]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3bv((const GLbyte *) a0);
+    return 0;
+}
+case 388: { /* secondary_color3d */
+    if (!qegl_gl_avail[388]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3d(a0, a1, a2);
+    return 0;
+}
+case 389: { /* secondary_color3dv */
+    if (!qegl_gl_avail[389]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3dv((const GLdouble *) a0);
+    return 0;
+}
+case 390: { /* secondary_color3f */
+    if (!qegl_gl_avail[390]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3f(a0, a1, a2);
+    return 0;
+}
+case 391: { /* secondary_color3fv */
+    if (!qegl_gl_avail[391]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3fv((const GLfloat *) a0);
+    return 0;
+}
+case 392: { /* secondary_color3i */
+    if (!qegl_gl_avail[392]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3i(a0, a1, a2);
+    return 0;
+}
+case 393: { /* secondary_color3iv */
+    if (!qegl_gl_avail[393]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3iv((const GLint *) a0);
+    return 0;
+}
+case 394: { /* secondary_color3s */
+    if (!qegl_gl_avail[394]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3s(a0, a1, a2);
+    return 0;
+}
+case 395: { /* secondary_color3sv */
+    if (!qegl_gl_avail[395]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3sv((const GLshort *) a0);
+    return 0;
+}
+case 396: { /* secondary_color3ub */
+    if (!qegl_gl_avail[396]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3ub(a0, a1, a2);
+    return 0;
+}
+case 397: { /* secondary_color3ubv */
+    if (!qegl_gl_avail[397]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3ubv((const GLubyte *) a0);
+    return 0;
+}
+case 398: { /* secondary_color3ui */
+    if (!qegl_gl_avail[398]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3ui(a0, a1, a2);
+    return 0;
+}
+case 399: { /* secondary_color3uiv */
+    if (!qegl_gl_avail[399]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3uiv((const GLuint *) a0);
+    return 0;
+}
+case 400: { /* secondary_color3us */
+    if (!qegl_gl_avail[400]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3us(a0, a1, a2);
+    return 0;
+}
+case 401: { /* secondary_color3usv */
+    if (!qegl_gl_avail[401]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSecondaryColor3usv((const GLushort *) a0);
+    return 0;
+}
+case 407: { /* color_table_parameterfv */
+    if (!qegl_gl_avail[407]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColorTableParameterfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 408: { /* color_table_parameteriv */
+    if (!qegl_gl_avail[408]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColorTableParameteriv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 409: { /* copy_color_table */
+    if (!qegl_gl_avail[409]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyColorTable(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 411: { /* get_color_table_parameterfv */
+    if (!qegl_gl_avail[411]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetColorTableParameterfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 412: { /* get_color_table_parameteriv */
+    if (!qegl_gl_avail[412]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetColorTableParameteriv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 414: { /* copy_color_sub_table */
+    if (!qegl_gl_avail[414]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyColorSubTable(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 417: { /* convolution_parameterf */
+    if (!qegl_gl_avail[417]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glConvolutionParameterf(a0, a1, a2);
+    return 0;
+}
+case 418: { /* convolution_parameterfv */
+    if (!qegl_gl_avail[418]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glConvolutionParameterfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 419: { /* convolution_parameteri */
+    if (!qegl_gl_avail[419]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glConvolutionParameteri(a0, a1, a2);
+    return 0;
+}
+case 420: { /* convolution_parameteriv */
+    if (!qegl_gl_avail[420]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glConvolutionParameteriv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 421: { /* copy_convolution_filter1D */
+    if (!qegl_gl_avail[421]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyConvolutionFilter1D(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 422: { /* copy_convolution_filter2D */
+    if (!qegl_gl_avail[422]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyConvolutionFilter2D(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 424: { /* get_convolution_parameterfv */
+    if (!qegl_gl_avail[424]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetConvolutionParameterfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 425: { /* get_convolution_parameteriv */
+    if (!qegl_gl_avail[425]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetConvolutionParameteriv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 429: { /* get_histogram_parameterfv */
+    if (!qegl_gl_avail[429]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetHistogramParameterfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 430: { /* get_histogram_parameteriv */
+    if (!qegl_gl_avail[430]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetHistogramParameteriv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 432: { /* get_minmax_parameterfv */
+    if (!qegl_gl_avail[432]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetMinmaxParameterfv(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 433: { /* get_minmax_parameteriv */
+    if (!qegl_gl_avail[433]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetMinmaxParameteriv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 434: { /* histogram */
+    if (!qegl_gl_avail[434]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glHistogram(a0, a1, a2, a3);
+    return 0;
+}
+case 435: { /* minmax */
+    if (!qegl_gl_avail[435]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glMinmax(a0, a1, a2);
+    return 0;
+}
+case 436: { /* reset_histogram */
+    if (!qegl_gl_avail[436]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glResetHistogram(a0);
+    return 0;
+}
+case 437: { /* reset_minmax */
+    if (!qegl_gl_avail[437]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glResetMinmax(a0);
+    return 0;
+}
+case 440: { /* copy_tex_sub_image3D */
+    if (!qegl_gl_avail[440]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    int32_t a6 = (int32_t) rd_u32(&r);
+    int32_t a7 = (int32_t) rd_u32(&r);
+    int32_t a8 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyTexSubImage3D(a0, a1, a2, a3, a4, a5, a6, a7, a8);
+    return 0;
+}
+case 441: { /* combiner_parameterfv_NV */
+    if (!qegl_gl_avail[441]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCombinerParameterfvNV(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 442: { /* combiner_parameterf_NV */
+    if (!qegl_gl_avail[442]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCombinerParameterfNV(a0, a1);
+    return 0;
+}
+case 443: { /* combiner_parameteriv_NV */
+    if (!qegl_gl_avail[443]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCombinerParameterivNV(a0, (const GLint *) a1);
+    return 0;
+}
+case 444: { /* combiner_parameteri_NV */
+    if (!qegl_gl_avail[444]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCombinerParameteriNV(a0, a1);
+    return 0;
+}
+case 445: { /* combiner_input_NV */
+    if (!qegl_gl_avail[445]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    uint32_t a4 = rd_u32(&r);
+    uint32_t a5 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCombinerInputNV(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 446: { /* combiner_output_NV */
+    if (!qegl_gl_avail[446]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    uint32_t a4 = rd_u32(&r);
+    uint32_t a5 = rd_u32(&r);
+    uint32_t a6 = rd_u32(&r);
+    uint32_t a7 = rd_u32(&r);
+    uint32_t a8 = rd_u32(&r);
+    uint32_t a9 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCombinerOutputNV(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
+    return 0;
+}
+case 447: { /* final_combiner_input_NV */
+    if (!qegl_gl_avail[447]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFinalCombinerInputNV(a0, a1, a2, a3);
+    return 0;
+}
+case 448: { /* get_combiner_input_parameterfv_NV */
+    if (!qegl_gl_avail[448]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    void *a4 = rd_out(&r, 64);
+    uint8_t t4[4096];
+    memset(t4, 0, sizeof(t4));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetCombinerInputParameterfvNV(a0, a1, a2, a3, (GLfloat *) t4);
+    memcpy(a4, t4, 64);
+    return 0;
+}
+case 449: { /* get_combiner_input_parameteriv_NV */
+    if (!qegl_gl_avail[449]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    void *a4 = rd_out(&r, 64);
+    uint8_t t4[4096];
+    memset(t4, 0, sizeof(t4));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetCombinerInputParameterivNV(a0, a1, a2, a3, (GLint *) t4);
+    memcpy(a4, t4, 64);
+    return 0;
+}
+case 450: { /* get_combiner_output_parameterfv_NV */
+    if (!qegl_gl_avail[450]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    void *a3 = rd_out(&r, 64);
+    uint8_t t3[4096];
+    memset(t3, 0, sizeof(t3));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetCombinerOutputParameterfvNV(a0, a1, a2, (GLfloat *) t3);
+    memcpy(a3, t3, 64);
+    return 0;
+}
+case 451: { /* get_combiner_output_parameteriv_NV */
+    if (!qegl_gl_avail[451]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    void *a3 = rd_out(&r, 64);
+    uint8_t t3[4096];
+    memset(t3, 0, sizeof(t3));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetCombinerOutputParameterivNV(a0, a1, a2, (GLint *) t3);
+    memcpy(a3, t3, 64);
+    return 0;
+}
+case 452: { /* get_final_combiner_input_parameterfv_NV */
+    if (!qegl_gl_avail[452]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetFinalCombinerInputParameterfvNV(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 453: { /* get_final_combiner_input_parameteriv_NV */
+    if (!qegl_gl_avail[453]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetFinalCombinerInputParameterivNV(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 454: { /* combiner_stage_parameterfv_NV */
+    if (!qegl_gl_avail[454]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCombinerStageParameterfvNV(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 455: { /* get_combiner_stage_parameterfv_NV */
+    if (!qegl_gl_avail[455]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetCombinerStageParameterfvNV(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 459: { /* sample_coverage */
+    if (!qegl_gl_avail[459]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSampleCoverage(a0, a1);
+    return 0;
+}
+case 471: { /* bind_program_ARB */
+    if (!qegl_gl_avail[471]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindProgramARB(a0, a1);
+    return 0;
+}
+case 472: { /* delete_programs_ARB */
+    if (!qegl_gl_avail[472]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteProgramsARB(a0, (const GLuint *) a1);
+    return 0;
+}
+case 474: { /* is_program_ARB */
+    if (!qegl_gl_avail[474]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsProgramARB(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 475: { /* vertex_attrib1s_ARB */
+    if (!qegl_gl_avail[475]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib1sARB(a0, a1);
+    return 0;
+}
+case 476: { /* vertex_attrib1f_ARB */
+    if (!qegl_gl_avail[476]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib1fARB(a0, a1);
+    return 0;
+}
+case 477: { /* vertex_attrib1d_ARB */
+    if (!qegl_gl_avail[477]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib1dARB(a0, a1);
+    return 0;
+}
+case 478: { /* vertex_attrib2s_ARB */
+    if (!qegl_gl_avail[478]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib2sARB(a0, a1, a2);
+    return 0;
+}
+case 479: { /* vertex_attrib2f_ARB */
+    if (!qegl_gl_avail[479]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib2fARB(a0, a1, a2);
+    return 0;
+}
+case 480: { /* vertex_attrib2d_ARB */
+    if (!qegl_gl_avail[480]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib2dARB(a0, a1, a2);
+    return 0;
+}
+case 481: { /* vertex_attrib3s_ARB */
+    if (!qegl_gl_avail[481]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib3sARB(a0, a1, a2, a3);
+    return 0;
+}
+case 482: { /* vertex_attrib3f_ARB */
+    if (!qegl_gl_avail[482]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib3fARB(a0, a1, a2, a3);
+    return 0;
+}
+case 483: { /* vertex_attrib3d_ARB */
+    if (!qegl_gl_avail[483]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib3dARB(a0, a1, a2, a3);
+    return 0;
+}
+case 484: { /* vertex_attrib4s_ARB */
+    if (!qegl_gl_avail[484]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4sARB(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 485: { /* vertex_attrib4f_ARB */
+    if (!qegl_gl_avail[485]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    float a4 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4fARB(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 486: { /* vertex_attrib4d_ARB */
+    if (!qegl_gl_avail[486]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    double a4 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4dARB(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 487: { /* vertex_attrib4Nub_ARB */
+    if (!qegl_gl_avail[487]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    uint32_t a4 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4NubARB(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 488: { /* vertex_attrib1sv_ARB */
+    if (!qegl_gl_avail[488]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib1svARB(a0, (const GLshort *) a1);
+    return 0;
+}
+case 489: { /* vertex_attrib1fv_ARB */
+    if (!qegl_gl_avail[489]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib1fvARB(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 490: { /* vertex_attrib1dv_ARB */
+    if (!qegl_gl_avail[490]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib1dvARB(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 491: { /* vertex_attrib2sv_ARB */
+    if (!qegl_gl_avail[491]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib2svARB(a0, (const GLshort *) a1);
+    return 0;
+}
+case 492: { /* vertex_attrib2fv_ARB */
+    if (!qegl_gl_avail[492]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib2fvARB(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 493: { /* vertex_attrib2dv_ARB */
+    if (!qegl_gl_avail[493]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib2dvARB(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 494: { /* vertex_attrib3sv_ARB */
+    if (!qegl_gl_avail[494]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib3svARB(a0, (const GLshort *) a1);
+    return 0;
+}
+case 495: { /* vertex_attrib3fv_ARB */
+    if (!qegl_gl_avail[495]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib3fvARB(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 496: { /* vertex_attrib3dv_ARB */
+    if (!qegl_gl_avail[496]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib3dvARB(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 497: { /* vertex_attrib4bv_ARB */
+    if (!qegl_gl_avail[497]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4bvARB(a0, (const GLbyte *) a1);
+    return 0;
+}
+case 498: { /* vertex_attrib4sv_ARB */
+    if (!qegl_gl_avail[498]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4svARB(a0, (const GLshort *) a1);
+    return 0;
+}
+case 499: { /* vertex_attrib4iv_ARB */
+    if (!qegl_gl_avail[499]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4ivARB(a0, (const GLint *) a1);
+    return 0;
+}
+case 500: { /* vertex_attrib4ubv_ARB */
+    if (!qegl_gl_avail[500]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4ubvARB(a0, (const GLubyte *) a1);
+    return 0;
+}
+case 501: { /* vertex_attrib4usv_ARB */
+    if (!qegl_gl_avail[501]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4usvARB(a0, (const GLushort *) a1);
+    return 0;
+}
+case 502: { /* vertex_attrib4uiv_ARB */
+    if (!qegl_gl_avail[502]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4uivARB(a0, (const GLuint *) a1);
+    return 0;
+}
+case 503: { /* vertex_attrib4fv_ARB */
+    if (!qegl_gl_avail[503]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4fvARB(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 504: { /* vertex_attrib4dv_ARB */
+    if (!qegl_gl_avail[504]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4dvARB(a0, (const GLdouble *) a1);
+    return 0;
+}
+case 505: { /* vertex_attrib4Nbv_ARB */
+    if (!qegl_gl_avail[505]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4NbvARB(a0, (const GLbyte *) a1);
+    return 0;
+}
+case 506: { /* vertex_attrib4Nsv_ARB */
+    if (!qegl_gl_avail[506]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4NsvARB(a0, (const GLshort *) a1);
+    return 0;
+}
+case 507: { /* vertex_attrib4Niv_ARB */
+    if (!qegl_gl_avail[507]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4NivARB(a0, (const GLint *) a1);
+    return 0;
+}
+case 508: { /* vertex_attrib4Nubv_ARB */
+    if (!qegl_gl_avail[508]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4NubvARB(a0, (const GLubyte *) a1);
+    return 0;
+}
+case 509: { /* vertex_attrib4Nusv_ARB */
+    if (!qegl_gl_avail[509]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4NusvARB(a0, (const GLushort *) a1);
+    return 0;
+}
+case 510: { /* vertex_attrib4Nuiv_ARB */
+    if (!qegl_gl_avail[510]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttrib4NuivARB(a0, (const GLuint *) a1);
+    return 0;
+}
+case 512: { /* enable_vertex_attrib_array_ARB */
+    if (!qegl_gl_avail[512]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEnableVertexAttribArrayARB(a0);
+    return 0;
+}
+case 513: { /* disable_vertex_attrib_array_ARB */
+    if (!qegl_gl_avail[513]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDisableVertexAttribArrayARB(a0);
+    return 0;
+}
+case 514: { /* get_vertex_attribdv_ARB */
+    if (!qegl_gl_avail[514]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetVertexAttribdvARB(a0, a1, (GLdouble *) t2);
+    memcpy(a2, t2, 128);
+    return 0;
+}
+case 515: { /* get_vertex_attribfv_ARB */
+    if (!qegl_gl_avail[515]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetVertexAttribfvARB(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 516: { /* get_vertex_attribiv_ARB */
+    if (!qegl_gl_avail[516]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetVertexAttribivARB(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 518: { /* program_env_parameter4d_ARB */
+    if (!qegl_gl_avail[518]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    double a4 = rd_f64(&r);
+    double a5 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramEnvParameter4dARB(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 519: { /* program_env_parameter4dv_ARB */
+    if (!qegl_gl_avail[519]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramEnvParameter4dvARB(a0, a1, (const GLdouble *) a2);
+    return 0;
+}
+case 520: { /* program_env_parameter4f_ARB */
+    if (!qegl_gl_avail[520]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    float a4 = rd_f32(&r);
+    float a5 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramEnvParameter4fARB(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 521: { /* program_env_parameter4fv_ARB */
+    if (!qegl_gl_avail[521]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramEnvParameter4fvARB(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 522: { /* program_local_parameter4d_ARB */
+    if (!qegl_gl_avail[522]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    double a2 = rd_f64(&r);
+    double a3 = rd_f64(&r);
+    double a4 = rd_f64(&r);
+    double a5 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramLocalParameter4dARB(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 523: { /* program_local_parameter4dv_ARB */
+    if (!qegl_gl_avail[523]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramLocalParameter4dvARB(a0, a1, (const GLdouble *) a2);
+    return 0;
+}
+case 524: { /* program_local_parameter4f_ARB */
+    if (!qegl_gl_avail[524]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    float a4 = rd_f32(&r);
+    float a5 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramLocalParameter4fARB(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 525: { /* program_local_parameter4fv_ARB */
+    if (!qegl_gl_avail[525]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramLocalParameter4fvARB(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 526: { /* get_program_env_parameterdv_ARB */
+    if (!qegl_gl_avail[526]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetProgramEnvParameterdvARB(a0, a1, (GLdouble *) t2);
+    memcpy(a2, t2, 128);
+    return 0;
+}
+case 527: { /* get_program_env_parameterfv_ARB */
+    if (!qegl_gl_avail[527]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetProgramEnvParameterfvARB(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 528: { /* get_program_local_parameterdv_ARB */
+    if (!qegl_gl_avail[528]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetProgramLocalParameterdvARB(a0, a1, (GLdouble *) t2);
+    memcpy(a2, t2, 128);
+    return 0;
+}
+case 529: { /* get_program_local_parameterfv_ARB */
+    if (!qegl_gl_avail[529]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetProgramLocalParameterfvARB(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 532: { /* get_programiv_ARB */
+    if (!qegl_gl_avail[532]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetProgramivARB(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 540: { /* point_parameterf */
+    if (!qegl_gl_avail[540]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPointParameterf(a0, a1);
+    return 0;
+}
+case 541: { /* point_parameterfv */
+    if (!qegl_gl_avail[541]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPointParameterfv(a0, (const GLfloat *) a1);
+    return 0;
+}
+case 542: { /* point_parameteri */
+    if (!qegl_gl_avail[542]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPointParameteri(a0, a1);
+    return 0;
+}
+case 543: { /* point_parameteriv */
+    if (!qegl_gl_avail[543]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPointParameteriv(a0, (const GLint *) a1);
+    return 0;
+}
+case 544: { /* fog_coordf */
+    if (!qegl_gl_avail[544]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogCoordf(a0);
+    return 0;
+}
+case 545: { /* fog_coordfv */
+    if (!qegl_gl_avail[545]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogCoordfv((const GLfloat *) a0);
+    return 0;
+}
+case 546: { /* fog_coordd */
+    if (!qegl_gl_avail[546]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogCoordd(a0);
+    return 0;
+}
+case 547: { /* fog_coorddv */
+    if (!qegl_gl_avail[547]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFogCoorddv((const GLdouble *) a0);
+    return 0;
+}
+case 569: { /* window_pos2d */
+    if (!qegl_gl_avail[569]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2d(a0, a1);
+    return 0;
+}
+case 570: { /* window_pos2dv */
+    if (!qegl_gl_avail[570]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2dv((const GLdouble *) a0);
+    return 0;
+}
+case 571: { /* window_pos2f */
+    if (!qegl_gl_avail[571]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2f(a0, a1);
+    return 0;
+}
+case 572: { /* window_pos2fv */
+    if (!qegl_gl_avail[572]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2fv((const GLfloat *) a0);
+    return 0;
+}
+case 573: { /* window_pos2i */
+    if (!qegl_gl_avail[573]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2i(a0, a1);
+    return 0;
+}
+case 574: { /* window_pos2iv */
+    if (!qegl_gl_avail[574]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2iv((const GLint *) a0);
+    return 0;
+}
+case 575: { /* window_pos2s */
+    if (!qegl_gl_avail[575]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2s(a0, a1);
+    return 0;
+}
+case 576: { /* window_pos2sv */
+    if (!qegl_gl_avail[576]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos2sv((const GLshort *) a0);
+    return 0;
+}
+case 577: { /* window_pos3d */
+    if (!qegl_gl_avail[577]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    double a2 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3d(a0, a1, a2);
+    return 0;
+}
+case 578: { /* window_pos3dv */
+    if (!qegl_gl_avail[578]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3dv((const GLdouble *) a0);
+    return 0;
+}
+case 579: { /* window_pos3f */
+    if (!qegl_gl_avail[579]) return QEGL_ERR_GL;
+    float a0 = rd_f32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3f(a0, a1, a2);
+    return 0;
+}
+case 580: { /* window_pos3fv */
+    if (!qegl_gl_avail[580]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3fv((const GLfloat *) a0);
+    return 0;
+}
+case 581: { /* window_pos3i */
+    if (!qegl_gl_avail[581]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3i(a0, a1, a2);
+    return 0;
+}
+case 582: { /* window_pos3iv */
+    if (!qegl_gl_avail[582]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3iv((const GLint *) a0);
+    return 0;
+}
+case 583: { /* window_pos3s */
+    if (!qegl_gl_avail[583]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3s(a0, a1, a2);
+    return 0;
+}
+case 584: { /* window_pos3sv */
+    if (!qegl_gl_avail[584]) return QEGL_ERR_GL;
+    const void *a0 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glWindowPos3sv((const GLshort *) a0);
+    return 0;
+}
+case 591: { /* delete_object_ARB */
+    if (!qegl_gl_avail[591]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteObjectARB(a0);
+    return 0;
+}
+case 593: { /* detach_object_ARB */
+    if (!qegl_gl_avail[593]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    GLuint a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDetachObjectARB(a0, a1);
+    return 0;
+}
+case 594: { /* create_shader_object_ARB */
+    if (!qegl_gl_avail[594]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glCreateShaderObjectARB(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 596: { /* compile_shader_ARB */
+    if (!qegl_gl_avail[596]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCompileShaderARB(a0);
+    return 0;
+}
+case 597: { /* create_program_object_ARB */
+    if (!qegl_gl_avail[597]) return QEGL_ERR_GL;
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glCreateProgramObjectARB();
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 598: { /* attach_object_ARB */
+    if (!qegl_gl_avail[598]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    GLuint a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glAttachObjectARB(a0, a1);
+    return 0;
+}
+case 599: { /* link_program_ARB */
+    if (!qegl_gl_avail[599]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glLinkProgramARB(a0);
+    return 0;
+}
+case 600: { /* use_program_object_ARB */
+    if (!qegl_gl_avail[600]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUseProgramObjectARB(a0);
+    return 0;
+}
+case 601: { /* validate_program_ARB */
+    if (!qegl_gl_avail[601]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glValidateProgramARB(a0);
+    return 0;
+}
+case 602: { /* uniform1f_ARB */
+    if (!qegl_gl_avail[602]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    float a1 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform1fARB(a0, a1);
+    return 0;
+}
+case 603: { /* uniform2f_ARB */
+    if (!qegl_gl_avail[603]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform2fARB(a0, a1, a2);
+    return 0;
+}
+case 604: { /* uniform3f_ARB */
+    if (!qegl_gl_avail[604]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform3fARB(a0, a1, a2, a3);
+    return 0;
+}
+case 605: { /* uniform4f_ARB */
+    if (!qegl_gl_avail[605]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    float a1 = rd_f32(&r);
+    float a2 = rd_f32(&r);
+    float a3 = rd_f32(&r);
+    float a4 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform4fARB(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 606: { /* uniform1i_ARB */
+    if (!qegl_gl_avail[606]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform1iARB(a0, a1);
+    return 0;
+}
+case 607: { /* uniform2i_ARB */
+    if (!qegl_gl_avail[607]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform2iARB(a0, a1, a2);
+    return 0;
+}
+case 608: { /* uniform3i_ARB */
+    if (!qegl_gl_avail[608]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform3iARB(a0, a1, a2, a3);
+    return 0;
+}
+case 609: { /* uniform4i_ARB */
+    if (!qegl_gl_avail[609]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform4iARB(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 610: { /* uniform1fv_ARB */
+    if (!qegl_gl_avail[610]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform1fvARB(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 611: { /* uniform2fv_ARB */
+    if (!qegl_gl_avail[611]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform2fvARB(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 612: { /* uniform3fv_ARB */
+    if (!qegl_gl_avail[612]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform3fvARB(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 613: { /* uniform4fv_ARB */
+    if (!qegl_gl_avail[613]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform4fvARB(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case 614: { /* uniform1iv_ARB */
+    if (!qegl_gl_avail[614]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform1ivARB(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 615: { /* uniform2iv_ARB */
+    if (!qegl_gl_avail[615]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform2ivARB(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 616: { /* uniform3iv_ARB */
+    if (!qegl_gl_avail[616]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform3ivARB(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 617: { /* uniform4iv_ARB */
+    if (!qegl_gl_avail[617]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    const void *a2 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform4ivARB(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case 618: { /* uniform_matrix2fv_ARB */
+    if (!qegl_gl_avail[618]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    const void *a3 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix2fvARB(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case 619: { /* uniform_matrix3fv_ARB */
+    if (!qegl_gl_avail[619]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    const void *a3 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix3fvARB(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case 620: { /* uniform_matrix4fv_ARB */
+    if (!qegl_gl_avail[620]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    const void *a3 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix4fvARB(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case 621: { /* get_object_parameterfv_ARB */
+    if (!qegl_gl_avail[621]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetObjectParameterfvARB(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 622: { /* get_object_parameteriv_ARB */
+    if (!qegl_gl_avail[622]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetObjectParameterivARB(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 627: { /* get_uniformfv_ARB */
+    if (!qegl_gl_avail[627]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetUniformfvARB(a0, a1, (GLfloat *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 628: { /* get_uniformiv_ARB */
+    if (!qegl_gl_avail[628]) return QEGL_ERR_GL;
+    GLuint a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetUniformivARB(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 635: { /* delete_queries */
+    if (!qegl_gl_avail[635]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteQueries(a0, (const GLuint *) a1);
+    return 0;
+}
+case 636: { /* is_query */
+    if (!qegl_gl_avail[636]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsQuery(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 637: { /* begin_query */
+    if (!qegl_gl_avail[637]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBeginQuery(a0, a1);
+    return 0;
+}
+case 638: { /* end_query */
+    if (!qegl_gl_avail[638]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEndQuery(a0);
+    return 0;
+}
+case 639: { /* get_queryiv */
+    if (!qegl_gl_avail[639]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetQueryiv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 640: { /* get_query_objectiv */
+    if (!qegl_gl_avail[640]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetQueryObjectiv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 641: { /* get_query_objectuiv */
+    if (!qegl_gl_avail[641]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetQueryObjectuiv(a0, a1, (GLuint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 642: { /* bind_buffer */
+    if (!qegl_gl_avail[642]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindBuffer(a0, a1);
+    return 0;
+}
+case 643: { /* delete_buffers */
+    if (!qegl_gl_avail[643]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteBuffers(a0, (const GLuint *) a1);
+    return 0;
+}
+case 645: { /* is_buffer */
+    if (!qegl_gl_avail[645]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsBuffer(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 651: { /* get_buffer_parameteriv */
+    if (!qegl_gl_avail[651]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetBufferParameteriv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 653: { /* depth_bounds_EXT */
+    if (!qegl_gl_avail[653]) return QEGL_ERR_GL;
+    double a0 = rd_f64(&r);
+    double a1 = rd_f64(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDepthBoundsEXT(a0, a1);
+    return 0;
+}
+case 655: { /* is_shader */
+    if (!qegl_gl_avail[655]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsShader(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 656: { /* is_program */
+    if (!qegl_gl_avail[656]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsProgram(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 657: { /* get_shaderiv */
+    if (!qegl_gl_avail[657]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetShaderiv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 658: { /* get_programiv */
+    if (!qegl_gl_avail[658]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetProgramiv(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 661: { /* stencil_func_separate */
+    if (!qegl_gl_avail[661]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glStencilFuncSeparate(a0, a1, a2, a3);
+    return 0;
+}
+case 662: { /* stencil_mask_separate */
+    if (!qegl_gl_avail[662]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glStencilMaskSeparate(a0, a1);
+    return 0;
+}
+case 665: { /* is_renderbuffer_EXT */
+    if (!qegl_gl_avail[665]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsRenderbufferEXT(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 666: { /* bind_renderbuffer_EXT */
+    if (!qegl_gl_avail[666]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindRenderbufferEXT(a0, a1);
+    return 0;
+}
+case 667: { /* delete_renderbuffers_EXT */
+    if (!qegl_gl_avail[667]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteRenderbuffersEXT(a0, (const GLuint *) a1);
+    return 0;
+}
+case 669: { /* renderbuffer_storage_EXT */
+    if (!qegl_gl_avail[669]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRenderbufferStorageEXT(a0, a1, a2, a3);
+    return 0;
+}
+case 670: { /* get_renderbuffer_parameteriv_EXT */
+    if (!qegl_gl_avail[670]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetRenderbufferParameterivEXT(a0, a1, (GLint *) t2);
+    memcpy(a2, t2, 64);
+    return 0;
+}
+case 671: { /* is_framebuffer_EXT */
+    if (!qegl_gl_avail[671]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsFramebufferEXT(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 673: { /* delete_framebuffers_EXT */
+    if (!qegl_gl_avail[673]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    const void *a1 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDeleteFramebuffersEXT(a0, (const GLuint *) a1);
+    return 0;
+}
+case 675: { /* check_framebuffer_status_EXT */
+    if (!qegl_gl_avail[675]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glCheckFramebufferStatusEXT(a0);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case 676: { /* framebuffer_texture1D_EXT */
+    if (!qegl_gl_avail[676]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFramebufferTexture1DEXT(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 677: { /* framebuffer_texture2D_EXT */
+    if (!qegl_gl_avail[677]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFramebufferTexture2DEXT(a0, a1, a2, a3, a4);
+    return 0;
+}
+case 678: { /* framebuffer_texture3D_EXT */
+    if (!qegl_gl_avail[678]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFramebufferTexture3DEXT(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case 679: { /* framebuffer_renderbuffer_EXT */
+    if (!qegl_gl_avail[679]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFramebufferRenderbufferEXT(a0, a1, a2, a3);
+    return 0;
+}
+case 680: { /* get_framebuffer_attachment_parameteriv_EXT */
+    if (!qegl_gl_avail[680]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    void *a3 = rd_out(&r, 64);
+    uint8_t t3[4096];
+    memset(t3, 0, sizeof(t3));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetFramebufferAttachmentParameterivEXT(a0, a1, a2, (GLint *) t3);
+    memcpy(a3, t3, 64);
+    return 0;
+}
+case 681: { /* generate_mipmap_EXT */
+    if (!qegl_gl_avail[681]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGenerateMipmapEXT(a0);
+    return 0;
+}
+case 684: { /* program_env_parameters4fv_EXT */
+    if (!qegl_gl_avail[684]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    const void *a3 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramEnvParameters4fvEXT(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case 685: { /* program_local_parameters4fv_EXT */
+    if (!qegl_gl_avail[685]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    const void *a3 = rd_data(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glProgramLocalParameters4fvEXT(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
diff --git a/hw/display/qegl-gl-names.inc b/hw/display/qegl-gl-names.inc
new file mode 100644
index 0000000..5f54dc6
--- /dev/null
+++ b/hw/display/qegl-gl-names.inc
@@ -0,0 +1,687 @@
+/* Generated by gen/gen_gl.py - host GL entry point per dispatch index */
+    "glAccum",
+    "glAlphaFunc",
+    "glAreTexturesResident",
+    "",
+    "glBegin",
+    "glBindTexture",
+    "",
+    "glBlendFunc",
+    "glCallList",
+    "",
+    "glClear",
+    "glClearAccum",
+    "glClearColor",
+    "glClearDepth",
+    "glClearIndex",
+    "glClearStencil",
+    "glClipPlane",
+    "glColor3b",
+    "glColor3bv",
+    "glColor3d",
+    "glColor3dv",
+    "glColor3f",
+    "glColor3fv",
+    "glColor3i",
+    "glColor3iv",
+    "glColor3s",
+    "glColor3sv",
+    "glColor3ub",
+    "glColor3ubv",
+    "glColor3ui",
+    "glColor3uiv",
+    "glColor3us",
+    "glColor3usv",
+    "glColor4b",
+    "glColor4bv",
+    "glColor4d",
+    "glColor4dv",
+    "glColor4f",
+    "glColor4fv",
+    "glColor4i",
+    "glColor4iv",
+    "glColor4s",
+    "glColor4sv",
+    "glColor4ub",
+    "glColor4ubv",
+    "glColor4ui",
+    "glColor4uiv",
+    "glColor4us",
+    "glColor4usv",
+    "glColorMask",
+    "glColorMaterial",
+    "",
+    "glCopyPixels",
+    "glCopyTexImage1D",
+    "glCopyTexImage2D",
+    "glCopyTexSubImage1D",
+    "glCopyTexSubImage2D",
+    "glCullFace",
+    "glDeleteLists",
+    "glDeleteTextures",
+    "glDepthFunc",
+    "glDepthMask",
+    "glDepthRange",
+    "glDisable",
+    "glDisableClientState",
+    "",
+    "",
+    "",
+    "",
+    "glEdgeFlag",
+    "",
+    "glEdgeFlagv",
+    "glEnable",
+    "glEnableClientState",
+    "glEnd",
+    "glEndList",
+    "glEvalCoord1d",
+    "glEvalCoord1dv",
+    "glEvalCoord1f",
+    "glEvalCoord1fv",
+    "glEvalCoord2d",
+    "glEvalCoord2dv",
+    "glEvalCoord2f",
+    "glEvalCoord2fv",
+    "glEvalMesh1",
+    "glEvalMesh2",
+    "glEvalPoint1",
+    "glEvalPoint2",
+    "",
+    "glFinish",
+    "glFlush",
+    "glFogf",
+    "glFogfv",
+    "glFogi",
+    "glFogiv",
+    "glFrontFace",
+    "glFrustum",
+    "",
+    "",
+    "glGetBooleanv",
+    "glGetClipPlane",
+    "glGetDoublev",
+    "glGetError",
+    "glGetFloatv",
+    "glGetIntegerv",
+    "glGetLightfv",
+    "glGetLightiv",
+    "",
+    "",
+    "",
+    "glGetMaterialfv",
+    "glGetMaterialiv",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glGetTexEnvfv",
+    "glGetTexEnviv",
+    "glGetTexGendv",
+    "glGetTexGenfv",
+    "glGetTexGeniv",
+    "",
+    "glGetTexLevelParameterfv",
+    "glGetTexLevelParameteriv",
+    "glGetTexParameterfv",
+    "glGetTexParameteriv",
+    "glHint",
+    "glIndexMask",
+    "",
+    "glIndexd",
+    "glIndexdv",
+    "glIndexf",
+    "glIndexfv",
+    "glIndexi",
+    "glIndexiv",
+    "glIndexs",
+    "glIndexsv",
+    "glIndexub",
+    "glIndexubv",
+    "glInitNames",
+    "",
+    "glIsEnabled",
+    "glIsList",
+    "glIsTexture",
+    "glLightModelf",
+    "glLightModelfv",
+    "glLightModeli",
+    "glLightModeliv",
+    "glLightf",
+    "glLightfv",
+    "glLighti",
+    "glLightiv",
+    "glLineStipple",
+    "glLineWidth",
+    "glListBase",
+    "glLoadIdentity",
+    "glLoadMatrixd",
+    "glLoadMatrixf",
+    "glLoadName",
+    "glLogicOp",
+    "",
+    "",
+    "",
+    "",
+    "glMapGrid1d",
+    "glMapGrid1f",
+    "glMapGrid2d",
+    "glMapGrid2f",
+    "glMaterialf",
+    "glMaterialfv",
+    "glMateriali",
+    "glMaterialiv",
+    "glMatrixMode",
+    "glMultMatrixd",
+    "glMultMatrixf",
+    "glNewList",
+    "glNormal3b",
+    "glNormal3bv",
+    "glNormal3d",
+    "glNormal3dv",
+    "glNormal3f",
+    "glNormal3fv",
+    "glNormal3i",
+    "glNormal3iv",
+    "glNormal3s",
+    "glNormal3sv",
+    "",
+    "glOrtho",
+    "glPassThrough",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glPixelTransferf",
+    "glPixelTransferi",
+    "glPixelZoom",
+    "glPointSize",
+    "glPolygonMode",
+    "glPolygonOffset",
+    "",
+    "glPopAttrib",
+    "",
+    "glPopMatrix",
+    "glPopName",
+    "glPrioritizeTextures",
+    "glPushAttrib",
+    "",
+    "glPushMatrix",
+    "glPushName",
+    "glRasterPos2d",
+    "glRasterPos2dv",
+    "glRasterPos2f",
+    "glRasterPos2fv",
+    "glRasterPos2i",
+    "glRasterPos2iv",
+    "glRasterPos2s",
+    "glRasterPos2sv",
+    "glRasterPos3d",
+    "glRasterPos3dv",
+    "glRasterPos3f",
+    "glRasterPos3fv",
+    "glRasterPos3i",
+    "glRasterPos3iv",
+    "glRasterPos3s",
+    "glRasterPos3sv",
+    "glRasterPos4d",
+    "glRasterPos4dv",
+    "glRasterPos4f",
+    "glRasterPos4fv",
+    "glRasterPos4i",
+    "glRasterPos4iv",
+    "glRasterPos4s",
+    "glRasterPos4sv",
+    "",
+    "",
+    "glRectd",
+    "glRectdv",
+    "glRectf",
+    "glRectfv",
+    "glRecti",
+    "glRectiv",
+    "glRects",
+    "glRectsv",
+    "glRenderMode",
+    "glRotated",
+    "glRotatef",
+    "glScaled",
+    "glScalef",
+    "glScissor",
+    "",
+    "glShadeModel",
+    "glStencilFunc",
+    "glStencilMask",
+    "glStencilOp",
+    "glTexCoord1d",
+    "glTexCoord1dv",
+    "glTexCoord1f",
+    "glTexCoord1fv",
+    "glTexCoord1i",
+    "glTexCoord1iv",
+    "glTexCoord1s",
+    "glTexCoord1sv",
+    "glTexCoord2d",
+    "glTexCoord2dv",
+    "glTexCoord2f",
+    "glTexCoord2fv",
+    "glTexCoord2i",
+    "glTexCoord2iv",
+    "glTexCoord2s",
+    "glTexCoord2sv",
+    "glTexCoord3d",
+    "glTexCoord3dv",
+    "glTexCoord3f",
+    "glTexCoord3fv",
+    "glTexCoord3i",
+    "glTexCoord3iv",
+    "glTexCoord3s",
+    "glTexCoord3sv",
+    "glTexCoord4d",
+    "glTexCoord4dv",
+    "glTexCoord4f",
+    "glTexCoord4fv",
+    "glTexCoord4i",
+    "glTexCoord4iv",
+    "glTexCoord4s",
+    "glTexCoord4sv",
+    "",
+    "glTexEnvf",
+    "glTexEnvfv",
+    "glTexEnvi",
+    "glTexEnviv",
+    "glTexGend",
+    "glTexGendv",
+    "glTexGenf",
+    "glTexGenfv",
+    "glTexGeni",
+    "glTexGeniv",
+    "",
+    "",
+    "glTexParameterf",
+    "glTexParameterfv",
+    "glTexParameteri",
+    "glTexParameteriv",
+    "",
+    "",
+    "glTranslated",
+    "glTranslatef",
+    "glVertex2d",
+    "glVertex2dv",
+    "glVertex2f",
+    "glVertex2fv",
+    "glVertex2i",
+    "glVertex2iv",
+    "glVertex2s",
+    "glVertex2sv",
+    "glVertex3d",
+    "glVertex3dv",
+    "glVertex3f",
+    "glVertex3fv",
+    "glVertex3i",
+    "glVertex3iv",
+    "glVertex3s",
+    "glVertex3sv",
+    "glVertex4d",
+    "glVertex4dv",
+    "glVertex4f",
+    "glVertex4fv",
+    "glVertex4i",
+    "glVertex4iv",
+    "glVertex4s",
+    "glVertex4sv",
+    "",
+    "glViewport",
+    "glBlendFuncSeparate",
+    "glBlendColor",
+    "glBlendEquation",
+    "",
+    "",
+    "glClientActiveTexture",
+    "glActiveTexture",
+    "glMultiTexCoord1d",
+    "glMultiTexCoord1dv",
+    "glMultiTexCoord1f",
+    "glMultiTexCoord1fv",
+    "glMultiTexCoord1i",
+    "glMultiTexCoord1iv",
+    "glMultiTexCoord1s",
+    "glMultiTexCoord1sv",
+    "glMultiTexCoord2d",
+    "glMultiTexCoord2dv",
+    "glMultiTexCoord2f",
+    "glMultiTexCoord2fv",
+    "glMultiTexCoord2i",
+    "glMultiTexCoord2iv",
+    "glMultiTexCoord2s",
+    "glMultiTexCoord2sv",
+    "glMultiTexCoord3d",
+    "glMultiTexCoord3dv",
+    "glMultiTexCoord3f",
+    "glMultiTexCoord3fv",
+    "glMultiTexCoord3i",
+    "glMultiTexCoord3iv",
+    "glMultiTexCoord3s",
+    "glMultiTexCoord3sv",
+    "glMultiTexCoord4d",
+    "glMultiTexCoord4dv",
+    "glMultiTexCoord4f",
+    "glMultiTexCoord4fv",
+    "glMultiTexCoord4i",
+    "glMultiTexCoord4iv",
+    "glMultiTexCoord4s",
+    "glMultiTexCoord4sv",
+    "glLoadTransposeMatrixd",
+    "glLoadTransposeMatrixf",
+    "glMultTransposeMatrixd",
+    "glMultTransposeMatrixf",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glSecondaryColor3b",
+    "glSecondaryColor3bv",
+    "glSecondaryColor3d",
+    "glSecondaryColor3dv",
+    "glSecondaryColor3f",
+    "glSecondaryColor3fv",
+    "glSecondaryColor3i",
+    "glSecondaryColor3iv",
+    "glSecondaryColor3s",
+    "glSecondaryColor3sv",
+    "glSecondaryColor3ub",
+    "glSecondaryColor3ubv",
+    "glSecondaryColor3ui",
+    "glSecondaryColor3uiv",
+    "glSecondaryColor3us",
+    "glSecondaryColor3usv",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glColorTableParameterfv",
+    "glColorTableParameteriv",
+    "glCopyColorTable",
+    "",
+    "glGetColorTableParameterfv",
+    "glGetColorTableParameteriv",
+    "",
+    "glCopyColorSubTable",
+    "",
+    "",
+    "glConvolutionParameterf",
+    "glConvolutionParameterfv",
+    "glConvolutionParameteri",
+    "glConvolutionParameteriv",
+    "glCopyConvolutionFilter1D",
+    "glCopyConvolutionFilter2D",
+    "",
+    "glGetConvolutionParameterfv",
+    "glGetConvolutionParameteriv",
+    "",
+    "",
+    "",
+    "glGetHistogramParameterfv",
+    "glGetHistogramParameteriv",
+    "",
+    "glGetMinmaxParameterfv",
+    "glGetMinmaxParameteriv",
+    "glHistogram",
+    "glMinmax",
+    "glResetHistogram",
+    "glResetMinmax",
+    "",
+    "",
+    "glCopyTexSubImage3D",
+    "glCombinerParameterfvNV",
+    "glCombinerParameterfNV",
+    "glCombinerParameterivNV",
+    "glCombinerParameteriNV",
+    "glCombinerInputNV",
+    "glCombinerOutputNV",
+    "glFinalCombinerInputNV",
+    "glGetCombinerInputParameterfvNV",
+    "glGetCombinerInputParameterivNV",
+    "glGetCombinerOutputParameterfvNV",
+    "glGetCombinerOutputParameterivNV",
+    "glGetFinalCombinerInputParameterfvNV",
+    "glGetFinalCombinerInputParameterivNV",
+    "glCombinerStageParameterfvNV",
+    "glGetCombinerStageParameterfvNV",
+    "",
+    "",
+    "",
+    "glSampleCoverage",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glBindProgramARB",
+    "glDeleteProgramsARB",
+    "",
+    "glIsProgramARB",
+    "glVertexAttrib1sARB",
+    "glVertexAttrib1fARB",
+    "glVertexAttrib1dARB",
+    "glVertexAttrib2sARB",
+    "glVertexAttrib2fARB",
+    "glVertexAttrib2dARB",
+    "glVertexAttrib3sARB",
+    "glVertexAttrib3fARB",
+    "glVertexAttrib3dARB",
+    "glVertexAttrib4sARB",
+    "glVertexAttrib4fARB",
+    "glVertexAttrib4dARB",
+    "glVertexAttrib4NubARB",
+    "glVertexAttrib1svARB",
+    "glVertexAttrib1fvARB",
+    "glVertexAttrib1dvARB",
+    "glVertexAttrib2svARB",
+    "glVertexAttrib2fvARB",
+    "glVertexAttrib2dvARB",
+    "glVertexAttrib3svARB",
+    "glVertexAttrib3fvARB",
+    "glVertexAttrib3dvARB",
+    "glVertexAttrib4bvARB",
+    "glVertexAttrib4svARB",
+    "glVertexAttrib4ivARB",
+    "glVertexAttrib4ubvARB",
+    "glVertexAttrib4usvARB",
+    "glVertexAttrib4uivARB",
+    "glVertexAttrib4fvARB",
+    "glVertexAttrib4dvARB",
+    "glVertexAttrib4NbvARB",
+    "glVertexAttrib4NsvARB",
+    "glVertexAttrib4NivARB",
+    "glVertexAttrib4NubvARB",
+    "glVertexAttrib4NusvARB",
+    "glVertexAttrib4NuivARB",
+    "",
+    "glEnableVertexAttribArrayARB",
+    "glDisableVertexAttribArrayARB",
+    "glGetVertexAttribdvARB",
+    "glGetVertexAttribfvARB",
+    "glGetVertexAttribivARB",
+    "",
+    "glProgramEnvParameter4dARB",
+    "glProgramEnvParameter4dvARB",
+    "glProgramEnvParameter4fARB",
+    "glProgramEnvParameter4fvARB",
+    "glProgramLocalParameter4dARB",
+    "glProgramLocalParameter4dvARB",
+    "glProgramLocalParameter4fARB",
+    "glProgramLocalParameter4fvARB",
+    "glGetProgramEnvParameterdvARB",
+    "glGetProgramEnvParameterfvARB",
+    "glGetProgramLocalParameterdvARB",
+    "glGetProgramLocalParameterfvARB",
+    "",
+    "",
+    "glGetProgramivARB",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glPointParameterf",
+    "glPointParameterfv",
+    "glPointParameteri",
+    "glPointParameteriv",
+    "glFogCoordf",
+    "glFogCoordfv",
+    "glFogCoordd",
+    "glFogCoorddv",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glWindowPos2d",
+    "glWindowPos2dv",
+    "glWindowPos2f",
+    "glWindowPos2fv",
+    "glWindowPos2i",
+    "glWindowPos2iv",
+    "glWindowPos2s",
+    "glWindowPos2sv",
+    "glWindowPos3d",
+    "glWindowPos3dv",
+    "glWindowPos3f",
+    "glWindowPos3fv",
+    "glWindowPos3i",
+    "glWindowPos3iv",
+    "glWindowPos3s",
+    "glWindowPos3sv",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glDeleteObjectARB",
+    "",
+    "glDetachObjectARB",
+    "glCreateShaderObjectARB",
+    "",
+    "glCompileShaderARB",
+    "glCreateProgramObjectARB",
+    "glAttachObjectARB",
+    "glLinkProgramARB",
+    "glUseProgramObjectARB",
+    "glValidateProgramARB",
+    "glUniform1fARB",
+    "glUniform2fARB",
+    "glUniform3fARB",
+    "glUniform4fARB",
+    "glUniform1iARB",
+    "glUniform2iARB",
+    "glUniform3iARB",
+    "glUniform4iARB",
+    "glUniform1fvARB",
+    "glUniform2fvARB",
+    "glUniform3fvARB",
+    "glUniform4fvARB",
+    "glUniform1ivARB",
+    "glUniform2ivARB",
+    "glUniform3ivARB",
+    "glUniform4ivARB",
+    "glUniformMatrix2fvARB",
+    "glUniformMatrix3fvARB",
+    "glUniformMatrix4fvARB",
+    "glGetObjectParameterfvARB",
+    "glGetObjectParameterivARB",
+    "",
+    "",
+    "",
+    "",
+    "glGetUniformfvARB",
+    "glGetUniformivARB",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glDeleteQueries",
+    "glIsQuery",
+    "glBeginQuery",
+    "glEndQuery",
+    "glGetQueryiv",
+    "glGetQueryObjectiv",
+    "glGetQueryObjectuiv",
+    "glBindBuffer",
+    "glDeleteBuffers",
+    "",
+    "glIsBuffer",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "glGetBufferParameteriv",
+    "",
+    "glDepthBoundsEXT",
+    "",
+    "glIsShader",
+    "glIsProgram",
+    "glGetShaderiv",
+    "glGetProgramiv",
+    "",
+    "",
+    "glStencilFuncSeparate",
+    "glStencilMaskSeparate",
+    "",
+    "",
+    "glIsRenderbufferEXT",
+    "glBindRenderbufferEXT",
+    "glDeleteRenderbuffersEXT",
+    "",
+    "glRenderbufferStorageEXT",
+    "glGetRenderbufferParameterivEXT",
+    "glIsFramebufferEXT",
+    "",
+    "glDeleteFramebuffersEXT",
+    "",
+    "glCheckFramebufferStatusEXT",
+    "glFramebufferTexture1DEXT",
+    "glFramebufferTexture2DEXT",
+    "glFramebufferTexture3DEXT",
+    "glFramebufferRenderbufferEXT",
+    "glGetFramebufferAttachmentParameterivEXT",
+    "glGenerateMipmapEXT",
+    "",
+    "",
+    "glProgramEnvParameters4fvEXT",
+    "glProgramLocalParameters4fvEXT",
diff --git a/hw/display/qegl-gl.c b/hw/display/qegl-gl.c
new file mode 100644
index 0000000..4ce33c9
--- /dev/null
+++ b/hw/display/qegl-gl.c
@@ -0,0 +1,1265 @@
+/*
+ * qegl GL command decoder: generated cases (qegl-gl-gen.inc, from gen/gen_gl.py) plus the
+ * hand-written ones that need guest/host semantics (images, client arrays, framebuffer 0).
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ */
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#define QEGL_GLFN_DEFINE
+#include "qegl.h"
+#include "qegl-index.h"
+#include "qegl-gl3-index.h"
+
+/* ------------------------------------------------------------------------------------------
+ * Host GL backends. native: libepoxy -> system opengl32 -> the GPU vendor's OpenGL driver.
+ * Mesa (zink = GL on Vulkan, d3d12 = GL on Direct3D 12, llvmpipe = CPU): a private
+ * opengl32.dll loaded by full path (qegl.c), every entry point resolved into qegl_glfn[].
+ * ------------------------------------------------------------------------------------------ */
+void *qegl_glfn[QEGL_GLFN_COUNT];
+bool qegl_glfn_on;
+HMODULE qegl_mesa_module;
+
+/* placeholder for entry points the Mesa build lacks: logs (rate-limited), returns 0 */
+static uintptr_t qegl_glfn_missing(void)
+{
+    static int logged;
+    if (logged++ < 20) {
+        fprintf(stderr, "qegl: guest op 0x%x called a GL function the host backend lacks\n",
+                qegl_dbg_op);
+    }
+    return 0;
+}
+
+/* Fill qegl_glfn[] from the Mesa module: exports first (wgl*, GL 1.1), then - once a context
+   is current - Mesa's wglGetProcAddress. Returns the number still missing. */
+int qegl_glfn_resolve(bool with_context)
+{
+    PROC (WINAPI *gpa)(LPCSTR) =
+        (void *)GetProcAddress(qegl_mesa_module, "wglGetProcAddress");
+    int i, missing = 0;
+    for (i = 0; i < QEGL_GLFN_COUNT; i++) {
+        void *p;
+        if (qegl_glfn[i] && qegl_glfn[i] != (void *)qegl_glfn_missing) {
+            continue;
+        }
+        p = (void *)GetProcAddress(qegl_mesa_module, qegl_glfn_names[i]);
+        if (!p && with_context && gpa) {
+            p = (void *)gpa(qegl_glfn_names[i]);
+            if ((uintptr_t)p <= 3 || (intptr_t)p == -1) {
+                p = NULL;
+            }
+        }
+        if (!p) {
+            p = (void *)qegl_glfn_missing;
+            missing++;
+            if (with_context) {
+                fprintf(stderr, "qegl: host backend lacks %s\n", qegl_glfn_names[i]);
+            }
+        }
+        qegl_glfn[i] = p;
+    }
+    return missing;
+}
+
+bool qegl_has_gl_extension(const char *ext)
+{
+    GLint n = 0, i;
+    if (!qegl_glfn_on) {
+        return epoxy_has_gl_extension(ext);
+    }
+    glGetIntegerv(GL_NUM_EXTENSIONS, &n);
+    for (i = 0; i < n; i++) {
+        const char *s = (const char *)glGetStringi(GL_EXTENSIONS, i);
+        if (s && !strcmp(s, ext)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+typedef struct Reader {
+    const uint8_t *p, *end;
+    int err;
+} Reader;
+
+static uint32_t rd_u32(Reader *r)
+{
+    uint32_t v;
+    if (r->end - r->p < 4) {
+        r->err = 1;
+        return 0;
+    }
+    memcpy(&v, r->p, 4);
+    r->p += 4;
+    return v;
+}
+
+static float rd_f32(Reader *r)
+{
+    uint32_t u = rd_u32(r);
+    float f;
+    memcpy(&f, &u, 4);
+    return f;
+}
+
+static double rd_f64(Reader *r)
+{
+    double d;
+    if (r->end - r->p < 8) {
+        r->err = 1;
+        return 0;
+    }
+    memcpy(&d, r->p, 8);
+    r->p += 8;
+    return d;
+}
+
+/* Inline input array: u32 length (0xffffffff = NULL) + data padded to 4. Short arrays are
+   copied into a zero-padded scratch area so a GL call that reads more elements than the guest
+   sent (pname tables disagree) never runs off the shared memory. */
+static uint8_t scratch_in[8][256];
+static int scratch_in_idx;
+
+static const void *rd_data_len(Reader *r, uint32_t *plen)
+{
+    uint32_t n = rd_u32(r);
+    const uint8_t *d;
+    if (r->err || n == 0xffffffffu) {
+        *plen = 0;
+        return NULL;
+    }
+    if ((((uint64_t)n + 3) & ~3ull) > (uint64_t)(r->end - r->p)) {   /* 64-bit: no wrap for n near 2^32 */
+        r->err = 1;
+        *plen = 0;
+        return NULL;
+    }
+    d = r->p;
+    r->p += (n + 3) & ~3u;
+    *plen = n;
+    if (n < 192) {
+        uint8_t *s = scratch_in[scratch_in_idx++ & 7];
+        memset(s, 0, sizeof(scratch_in[0]));
+        memcpy(s, d, n);
+        return s;
+    }
+    return d;
+}
+
+static const void *rd_data(Reader *r)
+{
+    uint32_t n;
+    return rd_data_len(r, &n);
+}
+
+/* Output area reserved by the guest at the current position */
+static void *rd_out(Reader *r, uint32_t n)
+{
+    void *o;
+    if ((uint64_t)n > (uint64_t)(r->end - r->p)) {
+        r->err = 1;
+        return NULL;
+    }
+    o = (void *)r->p;
+    r->p += n;
+    return o;
+}
+
+#define QEGL_DISPATCH_COUNT 686
+const char *qegl_gl_name(uint32_t index);
+static const char *const qegl_gl_names[QEGL_DISPATCH_COUNT] = {
+#include "qegl-gl-names.inc"
+};
+bool qegl_gl_avail[QEGL_DISPATCH_COUNT];
+
+/* Which generated entry points the host GL actually provides (epoxy aborts on missing ones) */
+void qegl_gl_init(void)
+{
+    HMODULE gl32 = qegl_glfn_on ? qegl_mesa_module : GetModuleHandleA("opengl32.dll");
+    int i, n = 0;
+    for (i = 0; i < QEGL_DISPATCH_COUNT; i++) {
+        const char *nm = qegl_gl_names[i];
+        PROC pa;
+        if (!nm[0]) {
+            continue;
+        }
+        pa = wglGetProcAddress(nm);
+        if ((uintptr_t)pa > 3 && (intptr_t)pa != -1) {
+            qegl_gl_avail[i] = true;
+        } else if (gl32 && GetProcAddress(gl32, nm)) {
+            qegl_gl_avail[i] = true;
+        }
+        if (!qegl_gl_avail[i] && qegl_debug) {
+            fprintf(stderr, "qegl: host lacks %s\n", nm);
+        }
+        n += qegl_gl_avail[i];
+    }
+    if (qegl_debug) {
+        fprintf(stderr, "qegl: %d generated GL entry points available on the host\n", n);
+    }
+}
+
+static int exec_generated(QEGLExec *e, uint32_t index, Reader r)
+{
+    switch (index) {
+#include "qegl-gl-gen.inc"
+    }
+    return -1;
+}
+
+/* ------------------------------------------------------------------------------------------
+ * Hand-written decoders (wire formats mirror src/QemuGLDriver/manual.c)
+ * ------------------------------------------------------------------------------------------ */
+
+/* point array `id` (guest numbering, see QA_* in qgld.h) at `ptr` */
+static void set_array(uint32_t id, GLint size, GLenum type, GLboolean norm, GLsizei stride,
+                      const void *ptr)
+{
+    /* norm bit 1: an integer attribute (the GL3 path's glVertexAttribIPointer) */
+    if ((norm & 2) && id >= 32 && id < 48) {
+        glVertexAttribIPointer(id - 32, size, type, stride, ptr);
+        return;
+    }
+    norm &= 1;
+    GLint prev_client;
+    switch (id) {
+    case 0: glVertexPointer(size, type, stride, ptr); return;
+    case 1: glNormalPointer(type, stride, ptr); return;
+    case 2: glColorPointer(size, type, stride, ptr); return;
+    case 3: glIndexPointer(type, stride, ptr); return;
+    case 4: glEdgeFlagPointer(stride, ptr); return;
+    case 5: glSecondaryColorPointer(size, type, stride, ptr); return;
+    case 6: glFogCoordPointer(type, stride, ptr); return;
+    }
+    if (id >= 8 && id < 16) {
+        glGetIntegerv(GL_CLIENT_ACTIVE_TEXTURE, &prev_client);
+        glClientActiveTexture(GL_TEXTURE0 + (id - 8));
+        glTexCoordPointer(size, type, stride, ptr);
+        glClientActiveTexture(prev_client);
+    } else if (id >= 32 && id < 48) {
+        glVertexAttribPointerARB(id - 32, size, type, norm, stride, ptr);
+    }
+}
+
+static int type_bytes(GLenum t)
+{
+    switch (t) {
+    case GL_BYTE: case GL_UNSIGNED_BYTE: return 1;
+    case GL_SHORT: case GL_UNSIGNED_SHORT: case GL_HALF_FLOAT: return 2;
+    case GL_INT: case GL_UNSIGNED_INT: case GL_FLOAT: return 4;
+    case GL_DOUBLE: return 8;
+    }
+    return 0;
+}
+
+/* bytes of one array element */
+static int elem_bytes(uint32_t size, GLenum type)
+{
+    if (type == GL_INT_2_10_10_10_REV || type == GL_UNSIGNED_INT_2_10_10_10_REV) {
+        return size == 4 ? 4 : 0;
+    }
+    return size * type_bytes(type);
+}
+
+/* client-memory arrays shipped with a draw: u32 narr, then per array
+   u32 id, size, type, normalized, i32 lo, data (packed elements lo..). Leaves the array
+   buffer binding at 0 (restored by the caller). */
+static int apply_arrays(Reader *r)
+{
+    uint32_t narr = rd_u32(r), i;
+    if (r->err || narr > 48) {
+        return -1;
+    }
+    for (i = 0; i < narr; i++) {
+        uint32_t id = rd_u32(r), size = rd_u32(r), type = rd_u32(r), norm = rd_u32(r);
+        int32_t lo = (int32_t)rd_u32(r);
+        uint32_t len;
+        const uint8_t *d = rd_data_len(r, &len);
+        int es = elem_bytes(size, type);
+        if (r->err || !d || !es || size > 4 || lo < 0) {
+            return -1;
+        }
+        set_array(id, size, type, norm, es, d - (intptr_t)lo * es);
+    }
+    return 0;
+}
+
+int qegl_apply_client_arrays(const uint8_t **p, const uint8_t *end)
+{
+    Reader r = { *p, end, 0 };
+    int ret = apply_arrays(&r);
+    *p = r.p;
+    return r.err ? -1 : ret;
+}
+
+/* image data: either inline (u32 len + bytes, len 0xffffffff = NULL) - always tightly packed,
+   host unpack alignment is 1 in every context - or, with length 0xfffffffe, an offset into
+   the bound pixel unpack buffer */
+/* A buffer offset from the guest is only passed to GL when a buffer object really is bound
+   there: with no buffer bound, GL takes the "offset" as a pointer into this process (and for
+   glReadPixels writes through it) - a guest bug would corrupt or crash QEMU. GL range-checks
+   offsets into bound buffers itself. */
+static bool buffer_bound(GLenum binding)
+{
+    GLint b = 0;
+    glGetIntegerv(binding, &b);
+    return b != 0;
+}
+
+/* bytes of a tightly packed image (unpack alignment 1, as set on every host context);
+   0 = unknown format/type (not checked) */
+static uint64_t image_bytes(GLenum format, GLenum type, uint32_t w, uint32_t h, uint32_t d)
+{
+    uint32_t comps, bytes;
+    switch (format) {
+    case GL_RED: case GL_GREEN: case GL_BLUE: case GL_ALPHA: case GL_LUMINANCE:
+    case GL_INTENSITY: case GL_DEPTH_COMPONENT: case GL_STENCIL_INDEX: case GL_COLOR_INDEX:
+    case GL_RED_INTEGER: case GL_GREEN_INTEGER: case GL_BLUE_INTEGER: case GL_ALPHA_INTEGER:
+        comps = 1; break;
+    case GL_LUMINANCE_ALPHA: case GL_RG: case GL_RG_INTEGER: case GL_DEPTH_STENCIL:
+        comps = 2; break;
+    case GL_RGB: case GL_BGR: case GL_RGB_INTEGER: case GL_BGR_INTEGER: comps = 3; break;
+    case GL_RGBA: case GL_BGRA: case GL_ABGR_EXT: case GL_RGBA_INTEGER: case GL_BGRA_INTEGER:
+        comps = 4; break;
+    case GL_YCBCR_422_APPLE: return (uint64_t)w * h * d * 2;
+    default: return 0;
+    }
+    switch (type) {
+    case GL_UNSIGNED_BYTE: case GL_BYTE: bytes = comps; break;
+    case GL_UNSIGNED_SHORT: case GL_SHORT: case GL_HALF_FLOAT: bytes = comps * 2; break;
+    case GL_UNSIGNED_INT: case GL_INT: case GL_FLOAT: bytes = comps * 4; break;
+    case GL_UNSIGNED_BYTE_3_3_2: case GL_UNSIGNED_BYTE_2_3_3_REV: bytes = 1; break;
+    case GL_UNSIGNED_SHORT_5_6_5: case GL_UNSIGNED_SHORT_5_6_5_REV:
+    case GL_UNSIGNED_SHORT_4_4_4_4: case GL_UNSIGNED_SHORT_4_4_4_4_REV:
+    case GL_UNSIGNED_SHORT_5_5_5_1: case GL_UNSIGNED_SHORT_1_5_5_5_REV: bytes = 2; break;
+    case GL_UNSIGNED_INT_8_8_8_8: case GL_UNSIGNED_INT_8_8_8_8_REV:
+    case GL_UNSIGNED_INT_10_10_10_2: case GL_UNSIGNED_INT_2_10_10_10_REV: bytes = 4; break;
+    case GL_UNSIGNED_INT_24_8: case GL_UNSIGNED_INT_10F_11F_11F_REV:
+    case GL_UNSIGNED_INT_5_9_9_9_REV: bytes = 4; break;
+    case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: bytes = 8; break;
+    default: return 0;
+    }
+    return (uint64_t)w * h * d * bytes;
+}
+
+static const void *rd_image(Reader *r, bool *pbo)
+{
+    if (r->end - r->p >= 4 && *(const uint32_t *)r->p == 0xfffffffeu) {
+        r->p += 4;
+        *pbo = true;
+        return (const void *)(uintptr_t)rd_u32(r);
+    }
+    *pbo = false;
+    return rd_data(r);
+}
+
+/* the image argument of a texture upload is usable: a PBO offset with a PBO bound, or client
+   data at least as large as the image */
+static bool image_ok(Reader *r, const void *d, bool pbo, uint64_t need)
+{
+    if (pbo) {
+        return buffer_bound(GL_PIXEL_UNPACK_BUFFER_BINDING);
+    }
+    return !d || !need || (uint64_t)(r->p - (const uint8_t *)d) >= need;
+}
+
+/* run `call` with no unpack buffer bound unless the data is an offset into one */
+#define WITH_UNPACK(pbo, call) do { \
+    GLint _ub = 0; \
+    if (!(pbo)) { \
+        glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &_ub); \
+        if (_ub) glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); \
+    } \
+    call; \
+    if (_ub) glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _ub); \
+} while (0)
+
+static int exec_manual(QEGLExec *e, uint32_t index, Reader *r)
+{
+    bool pbo = false;
+    GLint prev_ab = 0;
+    QEGLContext *c = e->ctx;
+    uint32_t a[12];
+    const void *d;
+    uint32_t dlen;
+    int i;
+
+#define RD(n) do { for (i = 0; i < (n); i++) a[i] = rd_u32(r); if (r->err) return QEGL_ERR_BAD_CMD; } while (0)
+    switch (index) {
+    case QI_draw_buffer:
+        RD(1);
+        c->draw_buffer = a[0];
+        if (c->guest_fbo) {
+            glDrawBuffer(a[0]);
+        } else {
+            qegl_apply_draw_buffer(e);
+        }
+        return 0;
+    case QI_read_buffer:
+        RD(1);
+        c->read_buffer = a[0];
+        if (c->guest_read_fbo) {
+            glReadBuffer(a[0]);
+        } else {
+            qegl_apply_draw_buffer(e);
+        }
+        return 0;
+    case QI_draw_buffers_ARB: {
+        GLenum bufs[16];
+        RD(1);
+        d = rd_data_len(r, &dlen);
+        if (r->err || a[0] > 16 || dlen < a[0] * 4) {
+            return QEGL_ERR_BAD_CMD;
+        }
+        memcpy(bufs, d, a[0] * 4);
+        if (c->guest_fbo) {
+            glDrawBuffers(a[0], bufs);
+        } else {
+            c->draw_buffer = a[0] ? bufs[0] : GL_NONE;
+            qegl_apply_draw_buffer(e);
+        }
+        return 0;
+    }
+    case QI_bind_framebuffer_EXT:
+        RD(2);
+        if (a[0] != GL_FRAMEBUFFER) {
+            /* GL_READ_FRAMEBUFFER / GL_DRAW_FRAMEBUFFER alone: as the GL3 path does */
+            return qegl_exec_gl3(e, Q3_glBindFramebuffer, (const uint8_t *)a, 8);
+        }
+        c->guest_fbo = a[1];
+        c->guest_read_fbo = a[1];
+        if (a[1]) {
+            if (!c->group->guest_fbos) {
+                c->group->guest_fbos = g_hash_table_new(g_direct_hash, g_direct_equal);
+            }
+            g_hash_table_add(c->group->guest_fbos, GUINT_TO_POINTER(a[1]));
+            glBindFramebufferEXT(a[0], a[1]);
+        } else {
+            qegl_bind_draw_surface(e);
+        }
+        return 0;
+    case QI_delete_framebuffers_EXT: {
+        /* detach before deleting: see qegl_fbo_detach_all */
+        const GLuint *names;
+        GLint rprev = 0;
+        bool any = false, rdel = false;
+        RD(1);
+        names = rd_data_len(r, &dlen);
+        if (r->err || !names || (uint64_t)a[0] * 4 > dlen) {
+            return QEGL_ERR_BAD_CMD;
+        }
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &rprev);
+        for (i = 0; i < (int)a[0]; i++) {
+            if (names[i] && c->group->guest_fbos &&
+                g_hash_table_remove(c->group->guest_fbos, GUINT_TO_POINTER(names[i]))) {
+                glBindFramebufferEXT(GL_FRAMEBUFFER, names[i]);
+                qegl_fbo_detach_all(GL_FRAMEBUFFER);
+                any = true;
+                if (names[i] == c->guest_fbo) {
+                    c->guest_fbo = 0;       /* deleting the bound fbo binds 0 (the surface) */
+                }
+                if (names[i] == c->guest_read_fbo) {
+                    c->guest_read_fbo = 0;
+                }
+                if ((GLint)names[i] == rprev) {
+                    rdel = true;
+                }
+            }
+        }
+        glDeleteFramebuffersEXT(a[0], names);
+        if (any) {
+            if (c->guest_fbo) {
+                glBindFramebufferEXT(GL_FRAMEBUFFER, c->guest_fbo);
+            } else {
+                qegl_bind_draw_surface(e);
+            }
+            if (rprev && !rdel && (GLuint)rprev != c->guest_fbo) {
+                glBindFramebufferEXT(GL_READ_FRAMEBUFFER, rprev);
+            } else if (rdel && c->guest_fbo && !c->guest_read_fbo) {
+                /* the GL3 path's read framebuffer went: read from the surface again */
+                QEGLSurface *rsf = qegl_find_surface(e->s, c->draw_surf);
+                glBindFramebufferEXT(GL_READ_FRAMEBUFFER,
+                                     rsf && rsf->group == c->group ? c->surf_fbo : 0);
+            }
+        }
+        return 0;
+    }
+    case QI_tex_image1D:
+        RD(7);
+        d = rd_image(r, &pbo);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        if (!image_ok(r, d, pbo, image_bytes(a[5], a[6], a[3], 1, 1))) return QEGL_ERR_RANGE;
+        WITH_UNPACK(pbo, glTexImage1D(a[0], a[1], a[2], a[3], a[4], a[5], a[6], d));
+        return 0;
+    case QI_tex_image2D:
+        RD(8);
+        d = rd_image(r, &pbo);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        if (!image_ok(r, d, pbo, image_bytes(a[6], a[7], a[3], a[4], 1))) return QEGL_ERR_RANGE;
+        WITH_UNPACK(pbo, glTexImage2D(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], d));
+        return 0;
+    case QI_tex_image3D:
+        RD(9);
+        d = rd_image(r, &pbo);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        if (!image_ok(r, d, pbo, image_bytes(a[7], a[8], a[3], a[4], a[5]))) return QEGL_ERR_RANGE;
+        WITH_UNPACK(pbo, glTexImage3D(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], d));
+        return 0;
+    case QI_tex_sub_image1D:
+        RD(6);
+        d = rd_image(r, &pbo);
+        if (r->err || (!d && !pbo)) return QEGL_ERR_BAD_CMD;
+        if (!image_ok(r, d, pbo, image_bytes(a[4], a[5], a[3], 1, 1))) return QEGL_ERR_RANGE;
+        WITH_UNPACK(pbo, glTexSubImage1D(a[0], a[1], a[2], a[3], a[4], a[5], d));
+        return 0;
+    case QI_tex_sub_image2D:
+        RD(8);
+        d = rd_image(r, &pbo);
+        if (r->err || (!d && !pbo)) return QEGL_ERR_BAD_CMD;
+        if (!image_ok(r, d, pbo, image_bytes(a[6], a[7], a[4], a[5], 1))) return QEGL_ERR_RANGE;
+        WITH_UNPACK(pbo, glTexSubImage2D(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], d));
+        return 0;
+    case QI_tex_sub_image3D:
+        RD(10);
+        d = rd_image(r, &pbo);
+        if (r->err || (!d && !pbo)) return QEGL_ERR_BAD_CMD;
+        if (!image_ok(r, d, pbo, image_bytes(a[8], a[9], a[5], a[6], a[7]))) return QEGL_ERR_RANGE;
+        WITH_UNPACK(pbo, glTexSubImage3D(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8],
+                                         a[9], d));
+        return 0;
+    case QI_call_lists: {
+        uint32_t esz;
+        RD(2);
+        d = rd_data_len(r, &dlen);
+        if (r->err || !d) return QEGL_ERR_BAD_CMD;
+        switch (a[1]) {
+        case GL_BYTE: case GL_UNSIGNED_BYTE: esz = 1; break;
+        case GL_SHORT: case GL_UNSIGNED_SHORT: case GL_2_BYTES: esz = 2; break;
+        case GL_3_BYTES: esz = 3; break;
+        case GL_INT: case GL_UNSIGNED_INT: case GL_FLOAT: case GL_4_BYTES: esz = 4; break;
+        default: return QEGL_ERR_GL;
+        }
+        if ((uint64_t)a[0] * esz > dlen) return QEGL_ERR_BAD_CMD;
+        glCallLists(a[0], a[1], d);
+        return 0;
+    }
+    case QI_vertex_pointer:         /* any array, offset into the bound array buffer */
+        RD(6);
+        if (!buffer_bound(GL_ARRAY_BUFFER_BINDING)) return QEGL_ERR_GL;
+        set_array(a[0], a[1], a[2], a[3], a[4], (const void *)(uintptr_t)a[5]);
+        return 0;
+    case QI_draw_arrays:
+        RD(3);
+        glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_ab);
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, 0);
+        if (apply_arrays(r) == 0) {
+            glDrawArrays(a[0], a[1], a[2]);
+        }
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, prev_ab);
+        return r->err ? QEGL_ERR_BAD_CMD : 0;
+    case QI_draw_range_elements: {  /* mode count type start end in_vbo (off|data) arrays */
+        const void *idx;
+        GLint prev_eb = 0;
+        RD(6);
+        if (a[5]) {
+            idx = (const void *)(uintptr_t)rd_u32(r);
+            if (!buffer_bound(GL_ELEMENT_ARRAY_BUFFER_BINDING)) return QEGL_ERR_GL;
+        } else {
+            idx = rd_data(r);
+            glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prev_eb);
+            if (prev_eb) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+        }
+        glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_ab);
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, 0);
+        if (!r->err && apply_arrays(r) == 0 && (a[5] || idx)) {
+            glDrawRangeElements(a[0], a[3], a[4], a[1], a[2], idx);
+        }
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, prev_ab);
+        if (prev_eb) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prev_eb);
+        return r->err ? QEGL_ERR_BAD_CMD : 0;
+    }
+    case QI_buffer_data:            /* target size data|NULL usage */
+        RD(2);
+        d = rd_data_len(r, &dlen);
+        a[2] = rd_u32(r);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        glBufferData(a[0], a[1], d && dlen >= a[1] ? d : NULL, a[2]);
+        return 0;
+    case QI_buffer_sub_data:        /* target offset data */
+        RD(2);
+        d = rd_data_len(r, &dlen);
+        if (r->err || !d) return QEGL_ERR_BAD_CMD;
+        glBufferSubData(a[0], a[1], dlen, d);
+        return 0;
+    case QI_read_pixels: {          /* x y w h format type pbo (offset | reply area) */
+        GLint pb = 0;
+        RD(7);
+        if (a[6]) {
+            a[7] = rd_u32(r);
+            if (r->err) return QEGL_ERR_BAD_CMD;
+            if (!buffer_bound(GL_PIXEL_PACK_BUFFER_BINDING)) return QEGL_ERR_GL;
+            glReadPixels(a[0], a[1], a[2], a[3], a[4], a[5], (void *)(uintptr_t)a[7]);
+            return 0;
+        }
+        dlen = rd_u32(r);
+        if (r->err || dlen > (uint32_t)(r->end - r->p)) return QEGL_ERR_BAD_CMD;
+        {
+            void *out = rd_out(r, (dlen + 3) & ~3u);
+            uint64_t need = image_bytes(a[4], a[5], a[2], a[3], 1);
+            if (r->err || !out) return QEGL_ERR_BAD_CMD;
+            /* GL writes the whole image (pack alignment 1): never past the reply area */
+            if (!need || need > dlen) return QEGL_ERR_RANGE;
+            glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &pb);
+            if (pb) glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+            if (qegl_glfn_on && a[5] == GL_UNSIGNED_INT_8_8_8_8 &&
+                (a[4] == GL_BGRA || a[4] == GL_RGBA)) {
+                /* Mesa has no fast path for the byte-swapped packed type Core Image reads
+                   with (zink: ~16 ms for 1024x768, a per-pixel conversion): read the _REV
+                   type (a straight copy) and swap the bytes here */
+                uint8_t *px = out;
+                uint64_t k;
+                glReadPixels(a[0], a[1], a[2], a[3], a[4], GL_UNSIGNED_INT_8_8_8_8_REV, out);
+                for (k = 0; k < need; k += 4) {
+                    uint32_t v;
+                    memcpy(&v, px + k, 4);
+                    v = __builtin_bswap32(v);
+                    memcpy(px + k, &v, 4);
+                }
+            } else {
+                glReadPixels(a[0], a[1], a[2], a[3], a[4], a[5], out);
+            }
+            if (pb) glBindBuffer(GL_PIXEL_PACK_BUFFER, pb);
+        }
+        return 0;
+    }
+    case QI_draw_pixels:            /* w h format type data */
+        RD(4);
+        d = rd_data(r);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        WITH_UNPACK(false, glDrawPixels(a[0], a[1], a[2], a[3], d));
+        return 0;
+    case QI_bitmap: {               /* w h xo yo xm ym data */
+        float f[4];
+        RD(2);
+        for (i = 0; i < 4; i++) f[i] = rd_f32(r);
+        d = rd_data(r);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        WITH_UNPACK(false, glBitmap(a[0], a[1], f[0], f[1], f[2], f[3], d));
+        return 0;
+    }
+    case QI_polygon_stipple:
+        d = rd_data_len(r, &dlen);
+        if (r->err || !d || dlen < 128) return QEGL_ERR_BAD_CMD;
+        WITH_UNPACK(false, glPolygonStipple(d));
+        return 0;
+    case QI_program_string_ARB:     /* target format data */
+        RD(2);
+        d = rd_data_len(r, &dlen);
+        if (r->err || !d) return QEGL_ERR_BAD_CMD;
+        if (qegl_debug >= 3 && e->ring >= 2) {
+            fprintf(stderr, "PROGRAM ring %d target 0x%x len %u:\n%.*s\nEND PROGRAM\n", e->ring,
+                    a[0], dlen, (int)dlen, (const char *)d);
+        }
+        {
+            /* an error left over from an earlier command must not be taken for a compile
+               failure: Core Image then reports "program error: position 0" and falls back to
+               the software renderer for that kernel (seen with downsampleBlur, i.e. CIBloom
+               and large-radius blurs) */
+            GLenum prior = glGetError(), err;
+            if (prior != GL_NO_ERROR) {
+                qegl_note_gl_error(e, prior);
+            }
+            glProgramStringARB(a[0], a[1], dlen, d);
+            err = glGetError();
+            if (c) {
+                c->prog_error_pos = err == GL_INVALID_OPERATION ? 0 : -1;
+            }
+            if (err == GL_INVALID_OPERATION && qegl_debug) {
+                static int logged;
+                if (logged++ < 20) {
+                    fprintf(stderr, "qegl: ring %d: program (target 0x%x, %u bytes) rejected: "
+                            "%s\n", e->ring, a[0], dlen,
+                            (const char *)glGetString(GL_PROGRAM_ERROR_STRING_ARB));
+                }
+            }
+            qegl_note_gl_error(e, err);
+        }
+        return 0;
+    case QI_get_integerv:
+    case QI_get_floatv:
+    case QI_get_doublev:
+    case QI_get_booleanv: {
+        /* GL_PROGRAM_ERROR_POSITION_ARB is answered from the last glProgramStringARB: the
+           NVIDIA driver crashes on the query in some states (seen with Core Image) */
+        Reader r2 = *r;
+        uint32_t pname = rd_u32(&r2), n = index == QI_get_doublev ? 128 :
+                         index == QI_get_booleanv ? 16 : 64;
+        void *out;
+        GLint v = c ? c->prog_error_pos : -1;
+        if (r2.err || pname != GL_PROGRAM_ERROR_POSITION_ARB) {
+            return -1;          /* the generated handler */
+        }
+        out = rd_out(&r2, n);
+        if (r2.err) {
+            return QEGL_ERR_BAD_CMD;
+        }
+        memset(out, 0, n);
+        if (index == QI_get_integerv) {
+            *(GLint *)out = v;
+        } else if (index == QI_get_floatv) {
+            *(GLfloat *)out = v;
+        } else if (index == QI_get_doublev) {
+            *(GLdouble *)out = v;
+        } else {
+            *(GLboolean *)out = v != 0;
+        }
+        return 0;
+    }
+    case QI_shader_source_ARB: {    /* shader, concatenated source */
+        const GLcharARB *src;
+        GLint n;
+        RD(1);
+        src = rd_data_len(r, &dlen);
+        if (r->err || !src) return QEGL_ERR_BAD_CMD;
+        n = dlen;
+        glShaderSourceARB(a[0], 1, &src, &n);
+        return 0;
+    }
+    case QI_get_info_log_ARB:       /* obj cap -> u32 length + text[cap] */
+    case QI_get_shader_source_ARB:
+    case QI_get_shader_info_log:
+    case QI_get_program_info_log: {
+        uint8_t *out;
+        GLsizei len = 0;
+        RD(2);
+        if (a[1] > (uint32_t)(r->end - r->p)) return QEGL_ERR_BAD_CMD;
+        out = rd_out(r, 4 + ((a[1] + 3) & ~3u));
+        if (r->err || !out) return QEGL_ERR_BAD_CMD;
+        if (a[1]) {
+            if (index == QI_get_info_log_ARB) {
+                glGetInfoLogARB(a[0], a[1], &len, (GLcharARB *)(out + 4));
+            } else if (index == QI_get_shader_source_ARB) {
+                glGetShaderSourceARB(a[0], a[1], &len, (GLcharARB *)(out + 4));
+            } else if (index == QI_get_shader_info_log) {
+                glGetShaderInfoLog(a[0], a[1], &len, (GLchar *)(out + 4));
+            } else {
+                glGetProgramInfoLog(a[0], a[1], &len, (GLchar *)(out + 4));
+            }
+        }
+        memcpy(out, &len, 4);
+        return 0;
+    }
+    case QI_get_uniform_location_ARB:   /* program name -> int */
+    case QI_get_attrib_location_ARB: {
+        uint8_t *out;
+        GLint v;
+        RD(1);
+        d = rd_data_len(r, &dlen);
+        out = rd_out(r, 4);
+        if (r->err || !d || !out || !dlen || ((const char *)d)[dlen - 1]) return QEGL_ERR_BAD_CMD;
+        v = index == QI_get_uniform_location_ARB ? glGetUniformLocationARB(a[0], d) :
+                                                   glGetAttribLocationARB(a[0], d);
+        memcpy(out, &v, 4);
+        return 0;
+    }
+    case QI_bind_attrib_location_ARB:   /* program index name */
+        RD(2);
+        d = rd_data_len(r, &dlen);
+        if (r->err || !d || !dlen || ((const char *)d)[dlen - 1]) return QEGL_ERR_BAD_CMD;
+        glBindAttribLocationARB(a[0], a[1], d);
+        return 0;
+    case QI_get_active_uniform_ARB:     /* program index cap -> len size type name[cap] */
+    case QI_get_active_attrib_ARB: {
+        uint8_t *out;
+        GLsizei len = 0;
+        GLint size = 0;
+        GLenum type = 0;
+        RD(3);
+        if (a[2] > (uint32_t)(r->end - r->p)) return QEGL_ERR_BAD_CMD;
+        out = rd_out(r, 12 + ((a[2] + 3) & ~3u));
+        if (r->err || !out) return QEGL_ERR_BAD_CMD;
+        if (a[2]) {
+            if (index == QI_get_active_uniform_ARB) {
+                glGetActiveUniformARB(a[0], a[1], a[2], &len, &size, &type,
+                                      (GLcharARB *)(out + 12));
+            } else {
+                glGetActiveAttribARB(a[0], a[1], a[2], &len, &size, &type,
+                                     (GLcharARB *)(out + 12));
+            }
+        }
+        memcpy(out, &len, 4);
+        memcpy(out + 4, &size, 4);
+        memcpy(out + 8, &type, 4);
+        return 0;
+    }
+    case QI_get_attached_objects_ARB: { /* container cap -> count objs[cap] */
+        uint8_t *out;
+        GLsizei n = 0;
+        GLhandleARB objs[256];
+        RD(2);
+        out = rd_out(r, 4 + 4 * a[1]);
+        if (r->err || !out || a[1] > 256) return QEGL_ERR_BAD_CMD;
+        glGetAttachedObjectsARB(a[0], a[1], &n, objs);
+        memcpy(out, &n, 4);
+        for (i = 0; i < n && i < (int)a[1]; i++) {
+            uint32_t h = (uint32_t)(uintptr_t)objs[i];
+            memcpy(out + 4 + 4 * i, &h, 4);
+        }
+        return 0;
+    }
+    }
+#undef RD
+    return -1;
+}
+
+/* Detach every attachment of the framebuffer object bound to `target`; framebuffer objects
+   are only deleted after this. The NVIDIA driver (610.88) shares framebuffer objects between
+   the contexts of a share group (GL says they are per context; host\wgltest\fboshare.c).
+   qegl used to give every context the same internal FBO names, so destroying one context
+   deleted an FBO its siblings still had bound, with a surface texture attached: the driver
+   then released that texture's references once too often, freed it while it still had a name
+   and kept updating the freed block - corrupting its slab allocator (texture objects are
+   0x5c0-byte blocks) and crashing minutes later at nvoglv64+0xa97613. Each context now has
+   FBO names of its own (QEGLContext.surf_fbo/rd_fbo); detaching first keeps any FBO deletion
+   (guest FBOs are shared by design) from releasing texture references at all. Found by
+   watching a texture object's reference count in a replayed Core Image session (NOTES). */
+void qegl_fbo_detach_all(GLenum target)
+{
+    static const GLenum att[] = {
+        GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3,
+        GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5, GL_COLOR_ATTACHMENT6, GL_COLOR_ATTACHMENT7,
+        GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT,
+    };
+    unsigned i;
+    for (i = 0; i < ARRAY_SIZE(att); i++) {
+        GLint type = GL_NONE;
+        glGetFramebufferAttachmentParameteriv(target, att[i],
+                                              GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
+        if (type == GL_TEXTURE) {
+            glFramebufferTexture2D(target, att[i], GL_TEXTURE_2D, 0, 0);
+        } else if (type == GL_RENDERBUFFER) {
+            glFramebufferRenderbuffer(target, att[i], GL_RENDERBUFFER, 0);
+        }
+    }
+}
+
+/* detach and delete all framebuffer objects the guest left in a share group (a context of
+   the group current, at the group's end) */
+void qegl_group_fbos_release(QEGLGroup *g)
+{
+    GHashTableIter it;
+    gpointer k;
+    if (!g->guest_fbos) {
+        return;
+    }
+    g_hash_table_iter_init(&it, g->guest_fbos);
+    while (g_hash_table_iter_next(&it, &k, NULL)) {
+        GLuint f = GPOINTER_TO_UINT(k);
+        glBindFramebufferEXT(GL_FRAMEBUFFER, f);
+        qegl_fbo_detach_all(GL_FRAMEBUFFER);
+        glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
+        glDeleteFramebuffersEXT(1, &f);
+    }
+    g_hash_table_destroy(g->guest_fbos);
+    g->guest_fbos = NULL;
+}
+
+/* Polygon stipple on Mesa backends: zink ignores it (zink_set_polygon_stipple is a stub).
+   While it is enabled and the fragment stage is plain fixed function (no program or shader,
+   no texturing, fog or colour sum), each polygon primitive runs with a fragment program of
+   ours that kills fragments by window position through a 32x32 pattern texture on an image
+   unit beyond the guest's texture units. Everything touched is restored afterwards. */
+static const char stipple_fp[] =
+    "!!ARBfp1.0\n"
+    "PARAM s = {0.03125, 0.03125, 0.0, 1.0};\n"
+    "PARAM h = {0.5, 0.5, 0.5, 0.5};\n"
+    "TEMP t;\n"
+    "MUL t, fragment.position, s;\n"
+    "TEX t, t, texture[%d], 2D;\n"
+    "SUB t, t, h;\n"
+    "KIL t.xxxx;\n"
+    "MOV result.color, fragment.color;\n"
+    "END\n";
+static GLint stipple_unit = -2;         /* -1: emulation unavailable */
+
+static bool stipple_engage(QEGLExec *e, QEGLContext *c)
+{
+    GLint v = 0, nunits = 8, i;
+    if (stipple_unit == -1 || !c->stipple_set || !glIsEnabled(GL_POLYGON_STIPPLE) ||
+        glIsEnabled(GL_FRAGMENT_PROGRAM_ARB) || glIsEnabled(GL_FOG) || glIsEnabled(GL_COLOR_SUM)) {
+        return false;
+    }
+    glGetIntegerv(GL_CURRENT_PROGRAM, &v);
+    if (v) {
+        return false;
+    }
+    glGetIntegerv(GL_LIST_INDEX, &v);   /* compiling a display list: our calls would go in it */
+    if (v) {
+        return false;
+    }
+    glGetIntegerv(GL_ACTIVE_TEXTURE, &c->st_active);
+    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &nunits);
+    for (i = 0; i < MIN(nunits, 16); i++) {
+        glActiveTexture(GL_TEXTURE0 + i);
+        if (glIsEnabled(GL_TEXTURE_1D) || glIsEnabled(GL_TEXTURE_2D) ||
+            glIsEnabled(GL_TEXTURE_3D) || glIsEnabled(GL_TEXTURE_CUBE_MAP) ||
+            glIsEnabled(GL_TEXTURE_RECTANGLE)) {
+            glActiveTexture(c->st_active);
+            return false;
+        }
+    }
+    if (stipple_unit == -2) {
+        GLint n = 0;
+        glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &n);
+        stipple_unit = n > nunits ? MIN(n, 32) - 1 : -1;
+        if (stipple_unit < 0) {
+            glActiveTexture(c->st_active);
+            return false;
+        }
+    }
+    glActiveTexture(GL_TEXTURE0 + stipple_unit);
+    glGetIntegerv(GL_TEXTURE_BINDING_2D, &c->st_tex);
+    if (!c->stipple_tex) {
+        c->stipple_tex = qegl_internal_name(e->s);
+        c->stipple_dirty = true;
+    }
+    glBindTexture(GL_TEXTURE_2D, c->stipple_tex);
+    if (c->stipple_dirty) {
+        uint8_t img[32 * 32];
+        GLint ub = 0;
+        for (i = 0; i < 32 * 32; i++) {     /* row 0 = window row 0 mod 32, MSB first */
+            img[i] = (c->stipple[(i / 32) * 4 + (i % 32) / 8] >> (7 - (i % 8))) & 1 ? 255 : 0;
+        }
+        glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &ub);
+        if (ub) {
+            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+        }
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE8, 32, 32, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
+                     img);
+        if (ub) {
+            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, ub);
+        }
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+        c->stipple_dirty = false;
+    }
+    glActiveTexture(c->st_active);
+    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &c->st_prog);
+    if (!c->stipple_prog) {
+        char src[sizeof(stipple_fp) + 8];
+        GLenum prior = glGetError(), err;
+        if (prior != GL_NO_ERROR) {
+            qegl_note_gl_error(e, prior);
+        }
+        snprintf(src, sizeof(src), stipple_fp, stipple_unit);
+        c->stipple_prog = qegl_internal_name(e->s);
+        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->stipple_prog);
+        glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(src), src);
+        err = glGetError();
+        if (err != GL_NO_ERROR) {
+            fprintf(stderr, "qegl: polygon stipple emulation unavailable (program: %s)\n",
+                    (const char *)glGetString(GL_PROGRAM_ERROR_STRING_ARB));
+            glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->st_prog);
+            glActiveTexture(GL_TEXTURE0 + stipple_unit);
+            glBindTexture(GL_TEXTURE_2D, c->st_tex);
+            glActiveTexture(c->st_active);
+            stipple_unit = -1;
+            return false;
+        }
+    } else {
+        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->stipple_prog);
+    }
+    glEnable(GL_FRAGMENT_PROGRAM_ARB);
+    c->stipple_on = true;
+    return true;
+}
+
+/* Smooth points on Mesa backends: zink draws GL_POINT_SMOOTH points as squares. For plain
+   fixed-function fragments (as for the stipple emulation) a glBegin(GL_POINTS) block runs as
+   point sprites 2 pixels larger than the point (the antialiased fringe of a point centred on
+   a pixel corner reaches beyond its size) with a fragment program of ours: coverage =
+   clamp(size / 2 + 0.5 - distance from the centre, 0, 1) multiplies the fragment's alpha (the application blends, as smooth
+   points need), fragments outside the circle are killed. The sprite coordinates come from the
+   last texture coordinate set (GL_COORD_REPLACE), unused while texturing is off. */
+static const char psmooth_fp[] =
+    "!!ARBfp1.0\n"
+    "PARAM p = program.local[0];\n"
+    "PARAM h = {0.5, 0.5, 0.0, 0.0};\n"
+    "PARAM k = {1e-8, 0.002, 0.0, 0.0};\n"
+    "TEMP d, r;\n"
+    "SUB d, fragment.texcoord[%d], h;\n"
+    "MUL d, d, p.x;\n"
+    "MUL r.x, d.x, d.x;\n"
+    "MAD r.x, d.y, d.y, r.x;\n"
+    "MAX r.x, r.x, k.x;\n"
+    "RSQ r.y, r.x;\n"
+    "MUL r.z, r.x, r.y;\n"
+    "ADD r.w, p.y, h.x;\n"
+    "SUB_SAT r.w, r.w, r.z;\n"
+    "SUB r.x, r.w, k.y;\n"
+    "KIL r.x;\n"
+    "MOV result.color.xyz, fragment.color;\n"
+    "MUL result.color.w, fragment.color.w, r.w;\n"
+    "END\n";
+static GLint psmooth_unit = -2;         /* texture coordinate set; -1: unavailable */
+
+static bool ff_fragments_plain(void)
+{
+    GLint v = 0, nunits = 8, active = 0, i;
+    if (glIsEnabled(GL_FRAGMENT_PROGRAM_ARB) || glIsEnabled(GL_FOG) ||
+        glIsEnabled(GL_COLOR_SUM)) {
+        return false;
+    }
+    glGetIntegerv(GL_CURRENT_PROGRAM, &v);
+    if (v) {
+        return false;
+    }
+    glGetIntegerv(GL_LIST_INDEX, &v);   /* compiling a display list: our calls would go in it */
+    if (v) {
+        return false;
+    }
+    glGetIntegerv(GL_ACTIVE_TEXTURE, &active);
+    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &nunits);
+    for (i = 0; i < MIN(nunits, 16); i++) {
+        glActiveTexture(GL_TEXTURE0 + i);
+        if (glIsEnabled(GL_TEXTURE_1D) || glIsEnabled(GL_TEXTURE_2D) ||
+            glIsEnabled(GL_TEXTURE_3D) || glIsEnabled(GL_TEXTURE_CUBE_MAP) ||
+            glIsEnabled(GL_TEXTURE_RECTANGLE)) {
+            glActiveTexture(active);
+            return false;
+        }
+    }
+    glActiveTexture(active);
+    return true;
+}
+
+static bool psmooth_engage(QEGLExec *e, QEGLContext *c)
+{
+    GLfloat size = 1;
+    if (psmooth_unit == -1 || !glIsEnabled(GL_POINT_SMOOTH) || glIsEnabled(GL_POINT_SPRITE) ||
+        !ff_fragments_plain()) {
+        return false;
+    }
+    if (psmooth_unit == -2) {
+        GLint n = 0;
+        glGetIntegerv(GL_MAX_TEXTURE_COORDS, &n);
+        psmooth_unit = n >= 2 ? MIN(n, 8) - 1 : -1;
+        if (psmooth_unit < 0) {
+            return false;
+        }
+    }
+    glGetFloatv(GL_POINT_SIZE, &size);
+    c->ps_size = size;
+    glGetIntegerv(GL_ACTIVE_TEXTURE, &c->ps_active);
+    glActiveTexture(GL_TEXTURE0 + psmooth_unit);
+    glGetTexEnviv(GL_POINT_SPRITE, GL_COORD_REPLACE, &c->ps_replace);
+    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
+    glActiveTexture(c->ps_active);
+    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &c->ps_prog);
+    if (!c->psmooth_prog) {
+        char src[sizeof(psmooth_fp) + 8];
+        GLenum prior = glGetError(), err;
+        if (prior != GL_NO_ERROR) {
+            qegl_note_gl_error(e, prior);
+        }
+        snprintf(src, sizeof(src), psmooth_fp, psmooth_unit);
+        c->psmooth_prog = qegl_internal_name(e->s);
+        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->psmooth_prog);
+        glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(src), src);
+        err = glGetError();
+        if (err != GL_NO_ERROR) {
+            fprintf(stderr, "qegl: smooth point emulation unavailable (program: %s)\n",
+                    (const char *)glGetString(GL_PROGRAM_ERROR_STRING_ARB));
+            glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->ps_prog);
+            glActiveTexture(GL_TEXTURE0 + psmooth_unit);
+            glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, c->ps_replace);
+            glActiveTexture(c->ps_active);
+            psmooth_unit = -1;
+            return false;
+        }
+    } else {
+        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->psmooth_prog);
+    }
+    /* x: sprite size, y: the point's radius */
+    glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, size + 2, size / 2, 0, 0);
+    glPointSize(size + 2);
+    glEnable(GL_POINT_SPRITE);
+    glEnable(GL_FRAGMENT_PROGRAM_ARB);
+    c->psmooth_on = true;
+    return true;
+}
+
+static void psmooth_release(QEGLContext *c)
+{
+    glDisable(GL_FRAGMENT_PROGRAM_ARB);
+    glDisable(GL_POINT_SPRITE);
+    glPointSize(c->ps_size);
+    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->ps_prog);
+    glActiveTexture(GL_TEXTURE0 + psmooth_unit);
+    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, c->ps_replace);
+    glActiveTexture(c->ps_active);
+    c->psmooth_on = false;
+}
+
+static void stipple_release(QEGLContext *c)
+{
+    glDisable(GL_FRAGMENT_PROGRAM_ARB);
+    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, c->st_prog);
+    glActiveTexture(GL_TEXTURE0 + stipple_unit);
+    glBindTexture(GL_TEXTURE_2D, c->st_tex);
+    glActiveTexture(c->st_active);
+    c->stipple_on = false;
+}
+
+/* does this command emit a vertex (inside glBegin/glEnd)? */
+static bool provokes_vertex(uint32_t index, const uint8_t *p, uint32_t len)
+{
+    uint32_t attr;
+    if ((index >= QI_vertex2d && index <= QI_vertex4sv) || index == QI_array_element ||
+        (index >= QI_eval_coord1d && index <= QI_eval_coord2fv) ||
+        index == QI_eval_point1 || index == QI_eval_point2) {
+        return true;
+    }
+    if (index >= QI_vertex_attrib1s_ARB && index <= QI_vertex_attrib4Nuiv_ARB && len >= 4) {
+        memcpy(&attr, p, 4);
+        return attr == 0;           /* generic attribute 0 is the vertex position */
+    }
+    return false;
+}
+
+static int exec_one(QEGLExec *e, uint32_t index, const uint8_t *p, uint32_t len)
+{
+    Reader r = { p, p + len, 0 };
+    int ret = exec_manual(e, index, &r);
+    if (ret >= 0) {
+        return ret;
+    }
+    r.p = p;
+    r.err = 0;
+    ret = exec_generated(e, index, r);
+    if (ret < 0) {
+        if (qegl_debug) {
+            fprintf(stderr, "qegl: unhandled GL op %u\n", index);
+        }
+        return QEGL_ERR_BAD_CMD;
+    }
+    return ret;
+}
+
+static int exec_gl_inner(QEGLExec *e, uint32_t index, const uint8_t *p, uint32_t len);
+
+/* QEGL_ERRTRACE=1 in QEMU's environment (diagnostics): check glGetError after every GL command
+   and name the command that raised it (the guest still gets the error, through the ring) */
+int qegl_exec_gl(QEGLExec *e, uint32_t index, const uint8_t *p, uint32_t len)
+{
+    static int errtrace = -1;
+    int ret;
+    if (errtrace < 0) {
+        errtrace = getenv("QEGL_ERRTRACE") != NULL;
+    }
+    ret = exec_gl_inner(e, index, p, len);
+    if (errtrace && e->ctx && !e->ctx->in_begin) {
+        GLenum ge = glGetError();
+        if (ge != GL_NO_ERROR) {
+            uint32_t w[4] = { 0, 0, 0, 0 };
+            memcpy(w, p, MIN(len, sizeof(w)));
+            fprintf(stderr, "qegl: errtrace ring %d %s -> 0x%x (args %x %x %x %x)\n", e->ring,
+                    qegl_gl_name(index), ge, w[0], w[1], w[2], w[3]);
+            qegl_note_gl_error(e, ge);
+        }
+    }
+    return ret;
+}
+
+static int exec_gl_inner(QEGLExec *e, uint32_t index, const uint8_t *p, uint32_t len)
+{
+    QEGLContext *c = e->ctx;
+    if (index >= QEGL_OP_GL3_BASE - QEGL_OP_GL_BASE) {
+        return qegl_exec_gl3(e, index - (QEGL_OP_GL3_BASE - QEGL_OP_GL_BASE), p, len);
+    }
+    /* Mesa backends split a GL_QUADS quad into triangles along v1-v3; Apple's renderers and
+       NVIDIA's GL along v0-v2, which shows whenever the four vertex colours differ (glcheck
+       alpha_blend, combine_multitex, clip_stencil). Smooth-shaded quads therefore go to Mesa
+       as one GL_POLYGON per quad, which it fans from v0 (flat shading keeps GL_QUADS: a
+       polygon would take its colour from the first vertex instead of the last). */
+    if (qegl_glfn_on && c) {
+        if (index >= QI_rectd && index <= QI_rectsv) {
+            /* a rectangle is a polygon: stipple applies */
+            bool st = stipple_engage(e, c);
+            int ret = exec_one(e, index, p, len);
+            if (st) {
+                stipple_release(c);
+            }
+            return ret;
+        }
+        if (index == QI_polygon_stipple) {
+            int ret = exec_one(e, index, p, len);
+            uint32_t n = 0;
+            if (ret == 0 && len >= 4 + 128) {
+                memcpy(&n, p, 4);
+                if (n >= 128) {
+                    memcpy(c->stipple, p + 4, 128);
+                    c->stipple_set = true;
+                    c->stipple_dirty = true;
+                }
+            }
+            return ret;
+        }
+        if (index == QI_end && (c->stipple_on || c->psmooth_on)) {
+            int ret;
+            c->quad_split = false;
+            ret = exec_one(e, index, p, len);
+            if (c->stipple_on) {
+                stipple_release(c);
+            }
+            if (c->psmooth_on) {
+                psmooth_release(c);
+            }
+            return ret;
+        }
+        if (index == QI_begin && len >= 4) {
+            uint32_t mode;
+            GLint sm = GL_FLAT;
+            memcpy(&mode, p, 4);
+            if (mode >= GL_TRIANGLES && mode <= GL_POLYGON) {
+                stipple_engage(e, c);           /* polygon primitives only */
+            } else if (mode == GL_POINTS) {
+                psmooth_engage(e, c);
+            }
+            if (mode == GL_QUADS) {
+                glGetIntegerv(GL_SHADE_MODEL, &sm);
+                if (sm == GL_SMOOTH) {
+                    c->quad_split = true;
+                    c->quad_n = 0;
+                    glBegin(GL_POLYGON);
+                    return 0;
+                }
+            }
+        } else if (c->quad_split) {
+            if (index == QI_end) {
+                c->quad_split = false;
+            } else if (provokes_vertex(index, p, len)) {
+                if (c->quad_n == 4) {
+                    /* the next quad: a new polygon only when its first vertex comes (an
+                       empty glBegin/glEnd pair broke the next primitives on Mesa) */
+                    glEnd();
+                    glBegin(GL_POLYGON);
+                    c->quad_n = 0;
+                }
+                c->quad_n++;
+                return exec_one(e, index, p, len);
+            }
+        }
+    }
+    return exec_one(e, index, p, len);
+}
+
+const char *qegl_gl_name(uint32_t index)
+{
+    if (index >= QEGL_OP_GL3_BASE - QEGL_OP_GL_BASE) {
+        return qegl_gl3_name(index - (QEGL_OP_GL3_BASE - QEGL_OP_GL_BASE));
+    }
+    return index < QEGL_DISPATCH_COUNT && qegl_gl_names[index] ? qegl_gl_names[index] : "?";
+}
diff --git a/hw/display/qegl-gl3-gen.inc b/hw/display/qegl-gl3-gen.inc
new file mode 100644
index 0000000..50ddf44
--- /dev/null
+++ b/hw/display/qegl-gl3-gen.inc
@@ -0,0 +1,693 @@
+/* Generated by qe-gl3/gen/gen_gl3.py - do not edit. GL 3.x decoders. */
+case Q3_glGetIntegeri_v: {
+    if (!qegl3_avail[Q3_glGetIntegeri_v]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetIntegeri_v(a0, a1, (GLint *) t2);
+    if (a2) memcpy(a2, t2, 64);
+    return 0;
+}
+case Q3_glGetInteger64i_v: {
+    if (!qegl3_avail[Q3_glGetInteger64i_v]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetInteger64i_v(a0, a1, (GLint64 *) t2);
+    if (a2) memcpy(a2, t2, 128);
+    return 0;
+}
+case Q3_glGetBooleani_v: {
+    if (!qegl3_avail[Q3_glGetBooleani_v]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 16);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetBooleani_v(a0, a1, (GLboolean *) t2);
+    if (a2) memcpy(a2, t2, 16);
+    return 0;
+}
+case Q3_glVertexAttribDivisor: {
+    if (!qegl3_avail[Q3_glVertexAttribDivisor]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttribDivisor(a0, a1);
+    return 0;
+}
+case Q3_glPrimitiveRestartIndex: {
+    if (!qegl3_avail[Q3_glPrimitiveRestartIndex]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPrimitiveRestartIndex(a0);
+    return 0;
+}
+case Q3_glVertexAttribI4i: {
+    if (!qegl3_avail[Q3_glVertexAttribI4i]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttribI4i(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glVertexAttribI4ui: {
+    if (!qegl3_avail[Q3_glVertexAttribI4ui]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    uint32_t a4 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttribI4ui(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glVertexAttribI4iv: {
+    if (!qegl3_avail[Q3_glVertexAttribI4iv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1_n;
+    const void *a1 = rd_data_len(&r, &a1_n);
+    if (!r.err && a1 && a1_n < (uint64_t) (4) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttribI4iv(a0, (const GLint *) a1);
+    return 0;
+}
+case Q3_glVertexAttribI4uiv: {
+    if (!qegl3_avail[Q3_glVertexAttribI4uiv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1_n;
+    const void *a1 = rd_data_len(&r, &a1_n);
+    if (!r.err && a1 && a1_n < (uint64_t) (4) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glVertexAttribI4uiv(a0, (const GLuint *) a1);
+    return 0;
+}
+case Q3_glGetVertexAttribIiv: {
+    if (!qegl3_avail[Q3_glGetVertexAttribIiv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetVertexAttribIiv(a0, a1, (GLint *) t2);
+    if (a2) memcpy(a2, t2, 64);
+    return 0;
+}
+case Q3_glGetVertexAttribIuiv: {
+    if (!qegl3_avail[Q3_glGetVertexAttribIuiv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetVertexAttribIuiv(a0, a1, (GLuint *) t2);
+    if (a2) memcpy(a2, t2, 64);
+    return 0;
+}
+case Q3_glUniform1ui: {
+    if (!qegl3_avail[Q3_glUniform1ui]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform1ui(a0, a1);
+    return 0;
+}
+case Q3_glUniform2ui: {
+    if (!qegl3_avail[Q3_glUniform2ui]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform2ui(a0, a1, a2);
+    return 0;
+}
+case Q3_glUniform3ui: {
+    if (!qegl3_avail[Q3_glUniform3ui]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform3ui(a0, a1, a2, a3);
+    return 0;
+}
+case Q3_glUniform4ui: {
+    if (!qegl3_avail[Q3_glUniform4ui]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    uint32_t a4 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform4ui(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glUniform1uiv: {
+    if (!qegl3_avail[Q3_glUniform1uiv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) (a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform1uiv(a0, a1, (const GLuint *) a2);
+    return 0;
+}
+case Q3_glUniform2uiv: {
+    if (!qegl3_avail[Q3_glUniform2uiv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) (2 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform2uiv(a0, a1, (const GLuint *) a2);
+    return 0;
+}
+case Q3_glUniform3uiv: {
+    if (!qegl3_avail[Q3_glUniform3uiv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) (3 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform3uiv(a0, a1, (const GLuint *) a2);
+    return 0;
+}
+case Q3_glUniform4uiv: {
+    if (!qegl3_avail[Q3_glUniform4uiv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) (4 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniform4uiv(a0, a1, (const GLuint *) a2);
+    return 0;
+}
+case Q3_glUniformMatrix2x3fv: {
+    if (!qegl3_avail[Q3_glUniformMatrix2x3fv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3_n;
+    const void *a3 = rd_data_len(&r, &a3_n);
+    if (!r.err && a3 && a3_n < (uint64_t) (6 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix2x3fv(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case Q3_glUniformMatrix3x2fv: {
+    if (!qegl3_avail[Q3_glUniformMatrix3x2fv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3_n;
+    const void *a3 = rd_data_len(&r, &a3_n);
+    if (!r.err && a3 && a3_n < (uint64_t) (6 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix3x2fv(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case Q3_glUniformMatrix2x4fv: {
+    if (!qegl3_avail[Q3_glUniformMatrix2x4fv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3_n;
+    const void *a3 = rd_data_len(&r, &a3_n);
+    if (!r.err && a3 && a3_n < (uint64_t) (8 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix2x4fv(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case Q3_glUniformMatrix4x2fv: {
+    if (!qegl3_avail[Q3_glUniformMatrix4x2fv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3_n;
+    const void *a3 = rd_data_len(&r, &a3_n);
+    if (!r.err && a3 && a3_n < (uint64_t) (8 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix4x2fv(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case Q3_glUniformMatrix3x4fv: {
+    if (!qegl3_avail[Q3_glUniformMatrix3x4fv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3_n;
+    const void *a3 = rd_data_len(&r, &a3_n);
+    if (!r.err && a3 && a3_n < (uint64_t) (12 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix3x4fv(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case Q3_glUniformMatrix4x3fv: {
+    if (!qegl3_avail[Q3_glUniformMatrix4x3fv]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3_n;
+    const void *a3 = rd_data_len(&r, &a3_n);
+    if (!r.err && a3 && a3_n < (uint64_t) (12 * a1) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformMatrix4x3fv(a0, a1, a2, (const GLfloat *) a3);
+    return 0;
+}
+case Q3_glGetUniformuiv: {
+    if (!qegl3_avail[Q3_glGetUniformuiv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetUniformuiv(a0, a1, (GLuint *) t2);
+    if (a2) memcpy(a2, t2, 64);
+    return 0;
+}
+case Q3_glGetFragDataLocation: {
+    if (!qegl3_avail[Q3_glGetFragDataLocation]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1_n;
+    const void *a1 = rd_data_len(&r, &a1_n);
+    if (!r.err && (!a1 || !a1_n || ((const char *) a1)[a1_n - 1])) return QEGL_ERR_BAD_CMD;
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glGetFragDataLocation(a0, (const GLchar *) a1);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case Q3_glBindFragDataLocation: {
+    if (!qegl3_avail[Q3_glBindFragDataLocation]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && (!a2 || !a2_n || ((const char *) a2)[a2_n - 1])) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindFragDataLocation(a0, a1, (const GLchar *) a2);
+    return 0;
+}
+case Q3_glBindBufferBase: {
+    if (!qegl3_avail[Q3_glBindBufferBase]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindBufferBase(a0, a1, a2);
+    return 0;
+}
+case Q3_glBindBufferRange: {
+    if (!qegl3_avail[Q3_glBindBufferRange]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindBufferRange(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glGetActiveUniformsiv: {
+    if (!qegl3_avail[Q3_glGetActiveUniformsiv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    if (a1 > 256) a1 = 256;
+    if (a1 < 0) a1 = 0;
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) (a1) * 4) return QEGL_ERR_BAD_CMD;
+    uint32_t a3 = rd_u32(&r);
+    void *a4 = rd_out(&r, 1024);
+    uint8_t t4[4096];
+    memset(t4, 0, sizeof(t4));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetActiveUniformsiv(a0, a1, (const GLuint *) a2, a3, (GLint *) t4);
+    if (a4) memcpy(a4, t4, 1024);
+    return 0;
+}
+case Q3_glGetUniformBlockIndex: {
+    if (!qegl3_avail[Q3_glGetUniformBlockIndex]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1_n;
+    const void *a1 = rd_data_len(&r, &a1_n);
+    if (!r.err && (!a1 || !a1_n || ((const char *) a1)[a1_n - 1])) return QEGL_ERR_BAD_CMD;
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glGetUniformBlockIndex(a0, (const GLchar *) a1);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case Q3_glUniformBlockBinding: {
+    if (!qegl3_avail[Q3_glUniformBlockBinding]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glUniformBlockBinding(a0, a1, a2);
+    return 0;
+}
+case Q3_glBeginTransformFeedback: {
+    if (!qegl3_avail[Q3_glBeginTransformFeedback]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBeginTransformFeedback(a0);
+    return 0;
+}
+case Q3_glEndTransformFeedback: {
+    if (!qegl3_avail[Q3_glEndTransformFeedback]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEndTransformFeedback();
+    return 0;
+}
+case Q3_glPauseTransformFeedback: {
+    if (!qegl3_avail[Q3_glPauseTransformFeedback]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glPauseTransformFeedback();
+    return 0;
+}
+case Q3_glResumeTransformFeedback: {
+    if (!qegl3_avail[Q3_glResumeTransformFeedback]) return QEGL_ERR_GL;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glResumeTransformFeedback();
+    return 0;
+}
+case Q3_glBindSampler: {
+    if (!qegl3_avail[Q3_glBindSampler]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    GLuint a1 = qegl3_map_sampler(e, rd_u32(&r), true);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBindSampler(a0, a1);
+    return 0;
+}
+case Q3_glSamplerParameteri: {
+    if (!qegl3_avail[Q3_glSamplerParameteri]) return QEGL_ERR_GL;
+    GLuint a0 = qegl3_map_sampler(e, rd_u32(&r), true);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSamplerParameteri(a0, a1, a2);
+    return 0;
+}
+case Q3_glSamplerParameterf: {
+    if (!qegl3_avail[Q3_glSamplerParameterf]) return QEGL_ERR_GL;
+    GLuint a0 = qegl3_map_sampler(e, rd_u32(&r), true);
+    uint32_t a1 = rd_u32(&r);
+    float a2 = rd_f32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSamplerParameterf(a0, a1, a2);
+    return 0;
+}
+case Q3_glSamplerParameteriv: {
+    if (!qegl3_avail[Q3_glSamplerParameteriv]) return QEGL_ERR_GL;
+    GLuint a0 = qegl3_map_sampler(e, rd_u32(&r), true);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) (qegl3_pname_count(a1)) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSamplerParameteriv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case Q3_glSamplerParameterfv: {
+    if (!qegl3_avail[Q3_glSamplerParameterfv]) return QEGL_ERR_GL;
+    GLuint a0 = qegl3_map_sampler(e, rd_u32(&r), true);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) (qegl3_pname_count(a1)) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glSamplerParameterfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case Q3_glGetSamplerParameteriv: {
+    if (!qegl3_avail[Q3_glGetSamplerParameteriv]) return QEGL_ERR_GL;
+    GLuint a0 = qegl3_map_sampler(e, rd_u32(&r), true);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetSamplerParameteriv(a0, a1, (GLint *) t2);
+    if (a2) memcpy(a2, t2, 64);
+    return 0;
+}
+case Q3_glGetSamplerParameterfv: {
+    if (!qegl3_avail[Q3_glGetSamplerParameterfv]) return QEGL_ERR_GL;
+    GLuint a0 = qegl3_map_sampler(e, rd_u32(&r), true);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 64);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetSamplerParameterfv(a0, a1, (GLfloat *) t2);
+    if (a2) memcpy(a2, t2, 64);
+    return 0;
+}
+case Q3_glQueryCounter: {
+    if (!qegl3_avail[Q3_glQueryCounter]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glQueryCounter(a0, a1);
+    return 0;
+}
+case Q3_glGetQueryObjecti64v: {
+    if (!qegl3_avail[Q3_glGetQueryObjecti64v]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetQueryObjecti64v(a0, a1, (GLint64 *) t2);
+    if (a2) memcpy(a2, t2, 128);
+    return 0;
+}
+case Q3_glGetQueryObjectui64v: {
+    if (!qegl3_avail[Q3_glGetQueryObjectui64v]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetQueryObjectui64v(a0, a1, (GLuint64 *) t2);
+    if (a2) memcpy(a2, t2, 128);
+    return 0;
+}
+case Q3_glCopyBufferSubData: {
+    if (!qegl3_avail[Q3_glCopyBufferSubData]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glCopyBufferSubData(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glGetBufferParameteri64v: {
+    if (!qegl3_avail[Q3_glGetBufferParameteri64v]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *a2 = rd_out(&r, 128);
+    uint8_t t2[4096];
+    memset(t2, 0, sizeof(t2));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetBufferParameteri64v(a0, a1, (GLint64 *) t2);
+    if (a2) memcpy(a2, t2, 128);
+    return 0;
+}
+case Q3_glFramebufferTextureLayer: {
+    if (!qegl3_avail[Q3_glFramebufferTextureLayer]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glFramebufferTextureLayer(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glBlitFramebuffer: {
+    if (!qegl3_avail[Q3_glBlitFramebuffer]) return QEGL_ERR_GL;
+    int32_t a0 = (int32_t) rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    int32_t a2 = (int32_t) rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    int32_t a6 = (int32_t) rd_u32(&r);
+    int32_t a7 = (int32_t) rd_u32(&r);
+    uint32_t a8 = rd_u32(&r);
+    uint32_t a9 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBlitFramebuffer(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
+    return 0;
+}
+case Q3_glRenderbufferStorageMultisample: {
+    if (!qegl3_avail[Q3_glRenderbufferStorageMultisample]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glRenderbufferStorageMultisample(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glClearBufferiv: {
+    if (!qegl3_avail[Q3_glClearBufferiv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) ((a0 == 0x1800 ? 4 : 1)) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearBufferiv(a0, a1, (const GLint *) a2);
+    return 0;
+}
+case Q3_glClearBufferuiv: {
+    if (!qegl3_avail[Q3_glClearBufferuiv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) ((a0 == 0x1800 ? 4 : 1)) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearBufferuiv(a0, a1, (const GLuint *) a2);
+    return 0;
+}
+case Q3_glClearBufferfv: {
+    if (!qegl3_avail[Q3_glClearBufferfv]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2_n;
+    const void *a2 = rd_data_len(&r, &a2_n);
+    if (!r.err && a2 && a2_n < (uint64_t) ((a0 == 0x1800 ? 4 : 1)) * 4) return QEGL_ERR_BAD_CMD;
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearBufferfv(a0, a1, (const GLfloat *) a2);
+    return 0;
+}
+case Q3_glClearBufferfi: {
+    if (!qegl3_avail[Q3_glClearBufferfi]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    float a2 = rd_f32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glClearBufferfi(a0, a1, a2, a3);
+    return 0;
+}
+case Q3_glTexStorage2D: {
+    if (!qegl3_avail[Q3_glTexStorage2D]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexStorage2D(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glTexStorage3D: {
+    if (!qegl3_avail[Q3_glTexStorage3D]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    int32_t a1 = (int32_t) rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    int32_t a4 = (int32_t) rd_u32(&r);
+    int32_t a5 = (int32_t) rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glTexStorage3D(a0, a1, a2, a3, a4, a5);
+    return 0;
+}
+case Q3_glGetInternalformativ: {
+    if (!qegl3_avail[Q3_glGetInternalformativ]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    int32_t a3 = (int32_t) rd_u32(&r);
+    if (a3 > 16) a3 = 16;
+    if (a3 < 0) a3 = 0;
+    void *a4 = rd_out(&r, 64);
+    uint8_t t4[4096];
+    memset(t4, 0, sizeof(t4));
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glGetInternalformativ(a0, a1, a2, a3, (GLint *) t4);
+    if (a4) memcpy(a4, t4, 64);
+    return 0;
+}
+case Q3_glColorMaski: {
+    if (!qegl3_avail[Q3_glColorMaski]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    uint32_t a4 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glColorMaski(a0, a1, a2, a3, a4);
+    return 0;
+}
+case Q3_glEnablei: {
+    if (!qegl3_avail[Q3_glEnablei]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glEnablei(a0, a1);
+    return 0;
+}
+case Q3_glDisablei: {
+    if (!qegl3_avail[Q3_glDisablei]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glDisablei(a0, a1);
+    return 0;
+}
+case Q3_glIsEnabledi: {
+    if (!qegl3_avail[Q3_glIsEnabledi]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    void *ro = rd_out(&r, 4);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    uint32_t rv = (uint32_t) glIsEnabledi(a0, a1);
+    memcpy(ro, &rv, 4);
+    return 0;
+}
+case Q3_glBlendEquationSeparate: {
+    if (!qegl3_avail[Q3_glBlendEquationSeparate]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glBlendEquationSeparate(a0, a1);
+    return 0;
+}
+case Q3_glStencilOpSeparate: {
+    if (!qegl3_avail[Q3_glStencilOpSeparate]) return QEGL_ERR_GL;
+    uint32_t a0 = rd_u32(&r);
+    uint32_t a1 = rd_u32(&r);
+    uint32_t a2 = rd_u32(&r);
+    uint32_t a3 = rd_u32(&r);
+    if (r.err) return QEGL_ERR_BAD_CMD;
+    glStencilOpSeparate(a0, a1, a2, a3);
+    return 0;
+}
diff --git a/hw/display/qegl-gl3-index.h b/hw/display/qegl-gl3-index.h
new file mode 100644
index 0000000..4164b99
--- /dev/null
+++ b/hw/display/qegl-gl3-index.h
@@ -0,0 +1,132 @@
+/* Generated by qe-gl3/gen/gen_gl3.py - GL3 command indices (op QEGL_OP_GL3_BASE + i) */
+#ifndef QEGL3_INDEX_H
+#define QEGL3_INDEX_H
+#define Q3_glGetString 0
+#define Q3_glGetStringi 1
+#define Q3_glGetIntegerv 2
+#define Q3_glGetBooleanv 3
+#define Q3_glGetFloatv 4
+#define Q3_glGetDoublev 5
+#define Q3_glGetInteger64v 6
+#define Q3_glGetIntegeri_v 7
+#define Q3_glGetInteger64i_v 8
+#define Q3_glGetBooleani_v 9
+#define Q3_glGenVertexArrays 10
+#define Q3_glBindVertexArray 11
+#define Q3_glDeleteVertexArrays 12
+#define Q3_glIsVertexArray 13
+#define Q3_glVertexAttribDivisor 14
+#define Q3_glVertexAttribIPointer 15
+#define Q3_glGetVertexAttribPointerv 16
+#define Q3_glDrawArraysInstanced 17
+#define Q3_glDrawElementsInstanced 18
+#define Q3_glPrimitiveRestartIndex 19
+#define Q3_glVertexAttribI4i 20
+#define Q3_glVertexAttribI4ui 21
+#define Q3_glVertexAttribI4iv 22
+#define Q3_glVertexAttribI4uiv 23
+#define Q3_glGetVertexAttribIiv 24
+#define Q3_glGetVertexAttribIuiv 25
+#define Q3_glUniform1ui 26
+#define Q3_glUniform2ui 27
+#define Q3_glUniform3ui 28
+#define Q3_glUniform4ui 29
+#define Q3_glUniform1uiv 30
+#define Q3_glUniform2uiv 31
+#define Q3_glUniform3uiv 32
+#define Q3_glUniform4uiv 33
+#define Q3_glUniformMatrix2x3fv 34
+#define Q3_glUniformMatrix3x2fv 35
+#define Q3_glUniformMatrix2x4fv 36
+#define Q3_glUniformMatrix4x2fv 37
+#define Q3_glUniformMatrix3x4fv 38
+#define Q3_glUniformMatrix4x3fv 39
+#define Q3_glGetUniformuiv 40
+#define Q3_glGetFragDataLocation 41
+#define Q3_glBindFragDataLocation 42
+#define Q3_glBindBufferBase 43
+#define Q3_glBindBufferRange 44
+#define Q3_glGetUniformIndices 45
+#define Q3_glGetActiveUniformsiv 46
+#define Q3_glGetUniformBlockIndex 47
+#define Q3_glGetActiveUniformBlockiv 48
+#define Q3_glGetActiveUniformBlockName 49
+#define Q3_glUniformBlockBinding 50
+#define Q3_glGenTransformFeedbacks 51
+#define Q3_glBindTransformFeedback 52
+#define Q3_glDeleteTransformFeedbacks 53
+#define Q3_glIsTransformFeedback 54
+#define Q3_glBeginTransformFeedback 55
+#define Q3_glEndTransformFeedback 56
+#define Q3_glPauseTransformFeedback 57
+#define Q3_glResumeTransformFeedback 58
+#define Q3_glTransformFeedbackVaryings 59
+#define Q3_glGetTransformFeedbackVarying 60
+#define Q3_glGenSamplers 61
+#define Q3_glDeleteSamplers 62
+#define Q3_glIsSampler 63
+#define Q3_glBindSampler 64
+#define Q3_glSamplerParameteri 65
+#define Q3_glSamplerParameterf 66
+#define Q3_glSamplerParameteriv 67
+#define Q3_glSamplerParameterfv 68
+#define Q3_glGetSamplerParameteriv 69
+#define Q3_glGetSamplerParameterfv 70
+#define Q3_glFenceSync 71
+#define Q3_glIsSync 72
+#define Q3_glDeleteSync 73
+#define Q3_glClientWaitSync 74
+#define Q3_glWaitSync 75
+#define Q3_glGetSynciv 76
+#define Q3_glQueryCounter 77
+#define Q3_glGetQueryObjecti64v 78
+#define Q3_glGetQueryObjectui64v 79
+#define Q3_glCopyBufferSubData 80
+#define Q3_glMapBufferRange 81
+#define Q3_glFlushMappedBufferRange 82
+#define Q3_glUnmapBuffer 83
+#define Q3_glGetBufferSubData 84
+#define Q3_glGetBufferParameteri64v 85
+#define Q3_glBindFramebuffer 86
+#define Q3_glDeleteFramebuffers 87
+#define Q3_glGenFramebuffers 88
+#define Q3_glIsFramebuffer 89
+#define Q3_glCheckFramebufferStatus 90
+#define Q3_glFramebufferTexture2D 91
+#define Q3_glFramebufferRenderbuffer 92
+#define Q3_glGetFramebufferAttachmentParameteriv 93
+#define Q3_glGenerateMipmap 94
+#define Q3_glGenRenderbuffers 95
+#define Q3_glBindRenderbuffer 96
+#define Q3_glDeleteRenderbuffers 97
+#define Q3_glIsRenderbuffer 98
+#define Q3_glRenderbufferStorage 99
+#define Q3_glGetRenderbufferParameteriv 100
+#define Q3_glFramebufferTextureLayer 101
+#define Q3_glBlitFramebuffer 102
+#define Q3_glRenderbufferStorageMultisample 103
+#define Q3_glInvalidateFramebuffer 104
+#define Q3_glInvalidateSubFramebuffer 105
+#define Q3_glClearBufferiv 106
+#define Q3_glClearBufferuiv 107
+#define Q3_glClearBufferfv 108
+#define Q3_glClearBufferfi 109
+#define Q3_glTexStorage2D 110
+#define Q3_glTexStorage3D 111
+#define Q3_glCompressedTexImage2D 112
+#define Q3_glCompressedTexImage3D 113
+#define Q3_glCompressedTexSubImage2D 114
+#define Q3_glCompressedTexSubImage3D 115
+#define Q3_glGetInternalformativ 116
+#define Q3_glColorMaski 117
+#define Q3_glEnablei 118
+#define Q3_glDisablei 119
+#define Q3_glIsEnabledi 120
+#define Q3_glBlendEquationSeparate 121
+#define Q3_glStencilOpSeparate 122
+#define Q3_qeglHostString 123
+#define Q3_qeglHostExtensions 124
+#define Q3_qeglGetSamplerBinding 125
+#define Q3_qeglPixelStore 126
+#define Q3_COUNT 127
+#endif
diff --git a/hw/display/qegl-gl3-names.inc b/hw/display/qegl-gl3-names.inc
new file mode 100644
index 0000000..e102a7f
--- /dev/null
+++ b/hw/display/qegl-gl3-names.inc
@@ -0,0 +1,128 @@
+/* Generated by qe-gl3/gen/gen_gl3.py - host GL entry point per GL3 index */
+    "glGetString",
+    "glGetStringi",
+    "glGetIntegerv",
+    "glGetBooleanv",
+    "glGetFloatv",
+    "glGetDoublev",
+    "glGetInteger64v",
+    "glGetIntegeri_v",
+    "glGetInteger64i_v",
+    "glGetBooleani_v",
+    "glGenVertexArrays",
+    "glBindVertexArray",
+    "glDeleteVertexArrays",
+    "glIsVertexArray",
+    "glVertexAttribDivisor",
+    "glVertexAttribIPointer",
+    "glGetVertexAttribPointerv",
+    "glDrawArraysInstanced",
+    "glDrawElementsInstanced",
+    "glPrimitiveRestartIndex",
+    "glVertexAttribI4i",
+    "glVertexAttribI4ui",
+    "glVertexAttribI4iv",
+    "glVertexAttribI4uiv",
+    "glGetVertexAttribIiv",
+    "glGetVertexAttribIuiv",
+    "glUniform1ui",
+    "glUniform2ui",
+    "glUniform3ui",
+    "glUniform4ui",
+    "glUniform1uiv",
+    "glUniform2uiv",
+    "glUniform3uiv",
+    "glUniform4uiv",
+    "glUniformMatrix2x3fv",
+    "glUniformMatrix3x2fv",
+    "glUniformMatrix2x4fv",
+    "glUniformMatrix4x2fv",
+    "glUniformMatrix3x4fv",
+    "glUniformMatrix4x3fv",
+    "glGetUniformuiv",
+    "glGetFragDataLocation",
+    "glBindFragDataLocation",
+    "glBindBufferBase",
+    "glBindBufferRange",
+    "glGetUniformIndices",
+    "glGetActiveUniformsiv",
+    "glGetUniformBlockIndex",
+    "glGetActiveUniformBlockiv",
+    "glGetActiveUniformBlockName",
+    "glUniformBlockBinding",
+    "glGenTransformFeedbacks",
+    "glBindTransformFeedback",
+    "glDeleteTransformFeedbacks",
+    "glIsTransformFeedback",
+    "glBeginTransformFeedback",
+    "glEndTransformFeedback",
+    "glPauseTransformFeedback",
+    "glResumeTransformFeedback",
+    "glTransformFeedbackVaryings",
+    "glGetTransformFeedbackVarying",
+    "glGenSamplers",
+    "glDeleteSamplers",
+    "glIsSampler",
+    "glBindSampler",
+    "glSamplerParameteri",
+    "glSamplerParameterf",
+    "glSamplerParameteriv",
+    "glSamplerParameterfv",
+    "glGetSamplerParameteriv",
+    "glGetSamplerParameterfv",
+    "glFenceSync",
+    "glIsSync",
+    "glDeleteSync",
+    "glClientWaitSync",
+    "glWaitSync",
+    "glGetSynciv",
+    "glQueryCounter",
+    "glGetQueryObjecti64v",
+    "glGetQueryObjectui64v",
+    "glCopyBufferSubData",
+    "glMapBufferRange",
+    "glFlushMappedBufferRange",
+    "glUnmapBuffer",
+    "glGetBufferSubData",
+    "glGetBufferParameteri64v",
+    "glBindFramebuffer",
+    "glDeleteFramebuffers",
+    "glGenFramebuffers",
+    "glIsFramebuffer",
+    "glCheckFramebufferStatus",
+    "glFramebufferTexture2D",
+    "glFramebufferRenderbuffer",
+    "glGetFramebufferAttachmentParameteriv",
+    "glGenerateMipmap",
+    "glGenRenderbuffers",
+    "glBindRenderbuffer",
+    "glDeleteRenderbuffers",
+    "glIsRenderbuffer",
+    "glRenderbufferStorage",
+    "glGetRenderbufferParameteriv",
+    "glFramebufferTextureLayer",
+    "glBlitFramebuffer",
+    "glRenderbufferStorageMultisample",
+    "glInvalidateFramebuffer",
+    "glInvalidateSubFramebuffer",
+    "glClearBufferiv",
+    "glClearBufferuiv",
+    "glClearBufferfv",
+    "glClearBufferfi",
+    "glTexStorage2D",
+    "glTexStorage3D",
+    "glCompressedTexImage2D",
+    "glCompressedTexImage3D",
+    "glCompressedTexSubImage2D",
+    "glCompressedTexSubImage3D",
+    "glGetInternalformativ",
+    "glColorMaski",
+    "glEnablei",
+    "glDisablei",
+    "glIsEnabledi",
+    "glBlendEquationSeparate",
+    "glStencilOpSeparate",
+    "qeglHostString",
+    "qeglHostExtensions",
+    "qeglGetSamplerBinding",
+    "qeglPixelStore",
diff --git a/hw/display/qegl-gl3.c b/hw/display/qegl-gl3.c
new file mode 100644
index 0000000..f743d6a
--- /dev/null
+++ b/hw/display/qegl-gl3.c
@@ -0,0 +1,841 @@
+/*
+ * qegl GL 3.x/4.x commands (op QEGL_OP_GL3_BASE + index): the entry points the guest's
+ * QemuGLDriver hands out through its GL3 lookup (WebGL 2 in Camino). Tiger's dispatch table
+ * has no slots for them; the index list and the generated decoders come from
+ * G:\Tiger\qe-gl3\gen\gen_gl3.py (qegl-gl3-gen.inc, qegl-gl3-names.inc, qegl-gl3-index.h).
+ *
+ * Object names: buffers, textures, renderbuffers, framebuffers, queries and programs keep the
+ * guest's names 1:1 as in the 1.5 path (compatibility contexts create them on first bind).
+ * Vertex array and transform feedback objects must come from glGen* and are per context
+ * (like framebuffer objects, see the NVIDIA FBO-sharing lesson in qe\NOTES.md): mapped
+ * guest -> host per QEGLContext. Sampler and sync objects are shared: mapped per QEGLGroup.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ */
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qegl.h"
+#include "qegl-gl3-index.h"
+
+bool qegl3_avail[Q3_COUNT];
+bool qegl_gl3_ok;
+
+static const char *const qegl3_names[Q3_COUNT] = {
+#include "qegl-gl3-names.inc"
+};
+
+const char *qegl_gl3_name(uint32_t index)
+{
+    return index < Q3_COUNT ? qegl3_names[index] : "?";
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* reader (same wire format as qegl-gl.c) */
+
+typedef struct Reader {
+    const uint8_t *p, *end;
+    int err;
+} Reader;
+
+static uint32_t rd_u32(Reader *r)
+{
+    uint32_t v;
+    if (r->end - r->p < 4) {
+        r->err = 1;
+        return 0;
+    }
+    memcpy(&v, r->p, 4);
+    r->p += 4;
+    return v;
+}
+
+static uint64_t rd_u64(Reader *r)
+{
+    uint64_t v;
+    if (r->end - r->p < 8) {
+        r->err = 1;
+        return 0;
+    }
+    memcpy(&v, r->p, 8);
+    r->p += 8;
+    return v;
+}
+
+static float rd_f32(Reader *r)
+{
+    uint32_t u = rd_u32(r);
+    float f;
+    memcpy(&f, &u, 4);
+    return f;
+}
+
+static G_GNUC_UNUSED double rd_f64(Reader *r)
+{
+    uint64_t u = rd_u64(r);
+    double d;
+    memcpy(&d, &u, 8);
+    return d;
+}
+
+/* inline input: u32 length (0xffffffff = NULL) + data padded to 4. Short data is copied into
+   zero-padded scratch so GL never reads past what the guest sent */
+static uint8_t scratch_in[8][256];
+static int scratch_in_idx;
+
+static const void *rd_data_len(Reader *r, uint32_t *plen)
+{
+    uint32_t n = rd_u32(r);
+    const uint8_t *d;
+    if (r->err || n == 0xffffffffu) {
+        *plen = 0;
+        return NULL;
+    }
+    if ((((uint64_t)n + 3) & ~3ull) > (uint64_t)(r->end - r->p)) {   /* 64-bit: no wrap for n near 2^32 */
+        r->err = 1;
+        *plen = 0;
+        return NULL;
+    }
+    d = r->p;
+    r->p += (n + 3) & ~3u;
+    *plen = n;
+    if (n < 192) {
+        uint8_t *s = scratch_in[scratch_in_idx++ & 7];
+        memset(s, 0, sizeof(scratch_in[0]));
+        memcpy(s, d, n);
+        return s;
+    }
+    return d;
+}
+
+static void *rd_out(Reader *r, uint32_t n)
+{
+    void *o;
+    if ((uint64_t)n > (uint64_t)(r->end - r->p)) {
+        r->err = 1;
+        return NULL;
+    }
+    o = (void *)r->p;
+    r->p += n;
+    return o;
+}
+
+static int qegl3_pname_count(GLenum pname)
+{
+    return pname == GL_TEXTURE_BORDER_COLOR ? 4 : 1;
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* name maps */
+
+static GLuint map_lookup(GHashTable **t, uint32_t name, bool *found)
+{
+    gpointer v = NULL;
+    *found = *t && g_hash_table_lookup_extended(*t, GUINT_TO_POINTER(name), NULL, &v);
+    return GPOINTER_TO_UINT(v);
+}
+
+static void map_set(GHashTable **t, uint32_t name, GLuint host)
+{
+    if (!*t) {
+        *t = g_hash_table_new(g_direct_hash, g_direct_equal);
+    }
+    g_hash_table_insert(*t, GUINT_TO_POINTER(name), GUINT_TO_POINTER(host));
+}
+
+GLuint qegl3_map_vao(QEGLExec *e, uint32_t name, bool create)
+{
+    bool found;
+    GLuint h;
+    if (!name || !e->ctx) {
+        return 0;
+    }
+    h = map_lookup(&e->ctx->vao_map, name, &found);
+    if (!found && create) {
+        glGenVertexArrays(1, &h);
+        map_set(&e->ctx->vao_map, name, h);
+    }
+    return h;
+}
+
+GLuint qegl3_map_tfo(QEGLExec *e, uint32_t name, bool create)
+{
+    bool found;
+    GLuint h;
+    if (!name || !e->ctx) {
+        return 0;
+    }
+    h = map_lookup(&e->ctx->tfo_map, name, &found);
+    if (!found && create) {
+        glGenTransformFeedbacks(1, &h);
+        map_set(&e->ctx->tfo_map, name, h);
+    }
+    return h;
+}
+
+/* samplers: a deleted name keeps a 0 entry, so it is not created again by a later use */
+GLuint qegl3_map_sampler(QEGLExec *e, uint32_t name, bool create)
+{
+    bool found;
+    GLuint h;
+    if (!name || !e->ctx) {
+        return 0;
+    }
+    h = map_lookup(&e->ctx->group->sampler_map, name, &found);
+    if (!found && create) {
+        glGenSamplers(1, &h);
+        map_set(&e->ctx->group->sampler_map, name, h);
+    }
+    return h;
+}
+
+static GLsync sync_lookup(QEGLExec *e, uint32_t id)
+{
+    QEGLGroup *g = e->ctx ? e->ctx->group : NULL;
+    return g && g->sync_map ? g_hash_table_lookup(g->sync_map, GUINT_TO_POINTER(id)) : NULL;
+}
+
+/* a context goes away: its vertex array and transform feedback objects go with it */
+void qegl_gl3_ctx_free(QEGLContext *c)
+{
+    if (c->vao_map) {
+        g_hash_table_destroy(c->vao_map);
+        c->vao_map = NULL;
+    }
+    if (c->tfo_map) {
+        g_hash_table_destroy(c->tfo_map);
+        c->tfo_map = NULL;
+    }
+}
+
+/* the share group's last context goes away (the group's root context is current) */
+void qegl_gl3_group_free(QEGLGroup *g)
+{
+    GHashTableIter it;
+    gpointer k, v;
+    if (g->sync_map) {
+        g_hash_table_iter_init(&it, g->sync_map);
+        while (g_hash_table_iter_next(&it, &k, &v)) {
+            glDeleteSync((GLsync)v);
+        }
+        g_hash_table_destroy(g->sync_map);
+        g->sync_map = NULL;
+    }
+    if (g->sampler_map) {
+        g_hash_table_iter_init(&it, g->sampler_map);
+        while (g_hash_table_iter_next(&it, &k, &v)) {
+            GLuint h = GPOINTER_TO_UINT(v);
+            if (h) {
+                glDeleteSamplers(1, &h);
+            }
+        }
+        g_hash_table_destroy(g->sampler_map);
+        g->sampler_map = NULL;
+    }
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* framebuffer bindings: the guest's framebuffer 0 is the context's surface (surf_fbo) */
+
+static void track_fbo(QEGLContext *c, GLuint fb)
+{
+    if (!c->group->guest_fbos) {
+        c->group->guest_fbos = g_hash_table_new(g_direct_hash, g_direct_equal);
+    }
+    g_hash_table_add(c->group->guest_fbos, GUINT_TO_POINTER(fb));
+}
+
+/* after the surface was bound as GL_FRAMEBUFFER: put back a separate guest read binding */
+void qegl3_fix_read_binding(QEGLExec *e)
+{
+    QEGLContext *c = e->ctx;
+    if (c && c->guest_read_fbo && c->guest_read_fbo != c->guest_fbo) {
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, c->guest_read_fbo);
+    }
+}
+
+static void bind_framebuffer(QEGLExec *e, GLenum target, GLuint fb)
+{
+    QEGLContext *c = e->ctx;
+    switch (target) {
+    case GL_FRAMEBUFFER:
+        c->guest_fbo = c->guest_read_fbo = fb;
+        if (fb) {
+            track_fbo(c, fb);
+            glBindFramebufferEXT(GL_FRAMEBUFFER, fb);
+        } else {
+            qegl_bind_draw_surface(e);
+        }
+        return;
+    case GL_DRAW_FRAMEBUFFER:
+        c->guest_fbo = fb;
+        if (fb) {
+            track_fbo(c, fb);
+            glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, fb);
+        } else {
+            qegl_bind_draw_surface(e);      /* both bindings: the read one is put back */
+            qegl3_fix_read_binding(e);
+        }
+        return;
+    case GL_READ_FRAMEBUFFER:
+        c->guest_read_fbo = fb;
+        if (fb) {
+            track_fbo(c, fb);
+            glBindFramebufferEXT(GL_READ_FRAMEBUFFER, fb);
+        } else {
+            qegl_bind_draw_surface(e);
+            if (c->guest_fbo) {
+                glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, c->guest_fbo);
+            }
+        }
+        return;
+    }
+}
+
+/* ------------------------------------------------------------------------------------------ */
+
+static GLenum binding_of(GLenum target)
+{
+    switch (target) {
+    case GL_ARRAY_BUFFER: return GL_ARRAY_BUFFER_BINDING;
+    case GL_ELEMENT_ARRAY_BUFFER: return GL_ELEMENT_ARRAY_BUFFER_BINDING;
+    case GL_PIXEL_PACK_BUFFER: return GL_PIXEL_PACK_BUFFER_BINDING;
+    case GL_PIXEL_UNPACK_BUFFER: return GL_PIXEL_UNPACK_BUFFER_BINDING;
+    case GL_UNIFORM_BUFFER: return GL_UNIFORM_BUFFER_BINDING;
+    case GL_TRANSFORM_FEEDBACK_BUFFER: return GL_TRANSFORM_FEEDBACK_BUFFER_BINDING;
+    case GL_COPY_READ_BUFFER: return GL_COPY_READ_BUFFER_BINDING;
+    case GL_COPY_WRITE_BUFFER: return GL_COPY_WRITE_BUFFER_BINDING;
+    case GL_TEXTURE_BUFFER: return GL_TEXTURE_BUFFER_BINDING;
+    }
+    return 0;
+}
+
+static bool bound(GLenum binding)
+{
+    GLint b = 0;
+    if (!binding) {
+        return false;
+    }
+    glGetIntegerv(binding, &b);
+    return b != 0;
+}
+
+/* split a NUL-separated name block into count strings; false if malformed */
+static bool split_names(const char *blk, uint32_t len, uint32_t count, const GLchar **out)
+{
+    uint32_t i, pos = 0;
+    if (!blk || !len || blk[len - 1]) {
+        return false;
+    }
+    for (i = 0; i < count; i++) {
+        if (pos >= len) {
+            return false;
+        }
+        out[i] = blk + pos;
+        pos += strlen(blk + pos) + 1;
+    }
+    return true;
+}
+
+static int index_bytes(GLenum type)
+{
+    return type == GL_UNSIGNED_BYTE ? 1 : type == GL_UNSIGNED_SHORT ? 2 :
+           type == GL_UNSIGNED_INT ? 4 : 0;
+}
+
+static int exec_manual3(QEGLExec *e, uint32_t index, Reader *r)
+{
+    QEGLContext *c = e->ctx;
+    uint32_t a[16];
+    const void *d;
+    uint32_t dlen;
+    uint8_t *out;
+    int i;
+    GLint prev_ab = 0;
+
+#define RD(n) do { for (i = 0; i < (n); i++) a[i] = rd_u32(r); if (r->err) return QEGL_ERR_BAD_CMD; } while (0)
+    switch (index) {
+    case Q3_qeglHostString: {
+        const char *s;
+        RD(1);
+        out = rd_out(r, 256);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        s = (const char *)glGetString(a[0]);
+        memset(out, 0, 256);
+        if (s) {
+            snprintf((char *)out, 256, "%s", s);
+        }
+        return 0;
+    }
+    case Q3_qeglHostExtensions: {
+        /* guest's candidate names (space separated) -> bitmask of those the host has */
+        char name[128];
+        uint32_t bit = 0, pos = 0, nwords;
+        d = rd_data_len(r, &dlen);
+        if (r->err || !d || !dlen || ((const char *)d)[dlen - 1]) return QEGL_ERR_BAD_CMD;
+        nwords = (uint32_t)(r->end - r->p) / 4;
+        out = rd_out(r, nwords * 4);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        memset(out, 0, nwords * 4);
+        while (pos < dlen && ((const char *)d)[pos]) {
+            uint32_t l = 0;
+            while (pos + l < dlen && ((const char *)d)[pos + l] != ' ' &&
+                   ((const char *)d)[pos + l]) {
+                l++;
+            }
+            if (l && l < sizeof(name) && bit / 32 < nwords) {
+                memcpy(name, (const char *)d + pos, l);
+                name[l] = 0;
+                if (qegl_has_gl_extension(name)) {
+                    ((uint32_t *)out)[bit / 32] |= 1u << (bit % 32);
+                }
+            }
+            bit++;
+            pos += l + 1;
+        }
+        return 0;
+    }
+    case Q3_qeglPixelStore: {
+        /* pack, swap_bytes lsb_first row_length image_height skip_rows skip_pixels skip_images
+           alignment: the guest's pixel store for a buffer-object upload / readback that follows
+           (client-memory data always arrives tightly packed: alignment 1, nothing else) */
+        static const GLenum un[8] = {
+            GL_UNPACK_SWAP_BYTES, GL_UNPACK_LSB_FIRST, GL_UNPACK_ROW_LENGTH,
+            GL_UNPACK_IMAGE_HEIGHT, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS,
+            GL_UNPACK_SKIP_IMAGES, GL_UNPACK_ALIGNMENT };
+        static const GLenum pk[8] = {
+            GL_PACK_SWAP_BYTES, GL_PACK_LSB_FIRST, GL_PACK_ROW_LENGTH, GL_PACK_IMAGE_HEIGHT,
+            GL_PACK_SKIP_ROWS, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_IMAGES, GL_PACK_ALIGNMENT };
+        RD(9);
+        for (i = 0; i < 8; i++) {
+            glPixelStorei(a[0] ? pk[i] : un[i], (GLint)a[1 + i]);
+        }
+        return 0;
+    }
+    case Q3_qeglGetSamplerBinding: {
+        GLint h = 0;
+        uint32_t v = 0;
+        out = rd_out(r, 4);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        glGetIntegerv(GL_SAMPLER_BINDING, &h);
+        if (h && c->group->sampler_map) {
+            GHashTableIter it;
+            gpointer k, val;
+            g_hash_table_iter_init(&it, c->group->sampler_map);
+            while (g_hash_table_iter_next(&it, &k, &val)) {
+                if (GPOINTER_TO_UINT(val) == (GLuint)h) {
+                    v = GPOINTER_TO_UINT(k);
+                    break;
+                }
+            }
+        }
+        memcpy(out, &v, 4);
+        return 0;
+    }
+    case Q3_glGetInteger64v: {
+        GLint64 t[32];
+        RD(1);
+        out = rd_out(r, 128);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        memset(t, 0, sizeof(t));
+        glGetInteger64v(a[0], t);
+        memcpy(out, t, 128);
+        return 0;
+    }
+    case Q3_glBindVertexArray:
+        RD(1);
+        glBindVertexArray(qegl3_map_vao(e, a[0], true));
+        return 0;
+    case Q3_glDeleteVertexArrays:
+    case Q3_glDeleteTransformFeedbacks:
+    case Q3_glDeleteSamplers: {
+        const uint32_t *names;
+        RD(1);
+        names = rd_data_len(r, &dlen);
+        if (r->err || !names || (uint64_t)a[0] * 4 > dlen) return QEGL_ERR_BAD_CMD;
+        for (i = 0; i < (int)a[0]; i++) {
+            bool found;
+            GLuint h;
+            if (!names[i]) {
+                continue;
+            }
+            if (index == Q3_glDeleteVertexArrays) {
+                h = map_lookup(&c->vao_map, names[i], &found);
+                if (found) {
+                    glDeleteVertexArrays(1, &h);
+                    g_hash_table_remove(c->vao_map, GUINT_TO_POINTER(names[i]));
+                }
+            } else if (index == Q3_glDeleteTransformFeedbacks) {
+                h = map_lookup(&c->tfo_map, names[i], &found);
+                if (found) {
+                    glDeleteTransformFeedbacks(1, &h);
+                    g_hash_table_remove(c->tfo_map, GUINT_TO_POINTER(names[i]));
+                }
+            } else {
+                h = map_lookup(&c->group->sampler_map, names[i], &found);
+                if (found && h) {
+                    glDeleteSamplers(1, &h);
+                }
+                map_set(&c->group->sampler_map, names[i], 0);   /* tombstone */
+            }
+        }
+        return 0;
+    }
+    case Q3_glIsSampler: {
+        bool found;
+        GLuint h;
+        uint32_t v;
+        RD(2);
+        out = rd_out(r, 4);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        h = map_lookup(&c->group->sampler_map, a[0], &found);
+        if (found) {
+            v = h && glIsSampler(h);
+        } else {
+            /* generated by the guest but never used: samplers exist from glGenSamplers on */
+            v = a[1] ? qegl3_map_sampler(e, a[0], true) != 0 : 0;
+        }
+        memcpy(out, &v, 4);
+        return 0;
+    }
+    case Q3_glDrawArraysInstanced:     /* mode first count instances arrays */
+        RD(4);
+        glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_ab);
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, 0);
+        if (qegl_apply_client_arrays(&r->p, r->end) == 0) {
+            glDrawArraysInstanced(a[0], a[1], a[2], a[3]);
+        } else {
+            r->err = 1;
+        }
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, prev_ab);
+        return r->err ? QEGL_ERR_BAD_CMD : 0;
+    case Q3_glDrawElementsInstanced: { /* mode count type instances in_vbo (off|data) arrays */
+        const void *idx;
+        GLint prev_eb = 0;
+        int is;
+        RD(5);
+        is = index_bytes(a[2]);
+        if (!is) return QEGL_ERR_GL;
+        if (a[4]) {
+            idx = (const void *)(uintptr_t)rd_u32(r);
+            if (!bound(GL_ELEMENT_ARRAY_BUFFER_BINDING)) return QEGL_ERR_GL;
+        } else {
+            idx = rd_data_len(r, &dlen);
+            if (r->err || !idx || (uint64_t)a[1] * is > dlen) return QEGL_ERR_BAD_CMD;
+            glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prev_eb);
+            if (prev_eb) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+        }
+        glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prev_ab);
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, 0);
+        if (!r->err && qegl_apply_client_arrays(&r->p, r->end) == 0) {
+            glDrawElementsInstanced(a[0], a[1], a[2], idx, a[3]);
+        } else {
+            r->err = 1;
+        }
+        if (prev_ab) glBindBuffer(GL_ARRAY_BUFFER, prev_ab);
+        if (prev_eb) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prev_eb);
+        return r->err ? QEGL_ERR_BAD_CMD : 0;
+    }
+    case Q3_glGetUniformIndices: {     /* program count len names -> indices[count] */
+        const GLchar *names[1024];
+        RD(2);
+        d = rd_data_len(r, &dlen);
+        if (r->err || a[1] > 1024) return QEGL_ERR_BAD_CMD;
+        out = rd_out(r, 4 * a[1]);
+        if (r->err || !split_names(d, dlen, a[1], names)) return QEGL_ERR_BAD_CMD;
+        memset(out, 0xff, 4 * a[1]);
+        glGetUniformIndices(a[0], a[1], names, (GLuint *)out);
+        return 0;
+    }
+    case Q3_glGetActiveUniformBlockiv: {   /* program index pname -> count values[256] */
+        GLint n = 1, small[16];
+        GLint *vals = small;
+        uint32_t cnt;
+        RD(3);
+        out = rd_out(r, 4 + 1024);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        memset(out, 0, 4 + 1024);
+        memset(small, 0, sizeof(small));
+        if (a[2] == GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) {
+            n = 0;
+            glGetActiveUniformBlockiv(a[0], a[1], GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &n);
+            if (n < 0) {
+                n = 0;
+            }
+            if (n > 16) {
+                vals = g_new0(GLint, n);
+            }
+        }
+        glGetActiveUniformBlockiv(a[0], a[1], a[2], vals);
+        cnt = MIN((uint32_t)n, 256);
+        memcpy(out, &cnt, 4);
+        memcpy(out + 4, vals, 4 * cnt);
+        if (vals != small) {
+            g_free(vals);
+        }
+        return 0;
+    }
+    case Q3_glGetActiveUniformBlockName: {     /* program index cap -> len name[cap] */
+        GLsizei len = 0;
+        RD(3);
+        if (a[2] > 4096) return QEGL_ERR_BAD_CMD;
+        out = rd_out(r, 4 + ((a[2] + 3) & ~3u));
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        memset(out, 0, 4 + ((a[2] + 3) & ~3u));
+        if (a[2]) {
+            glGetActiveUniformBlockName(a[0], a[1], a[2], &len, (GLchar *)out + 4);
+        }
+        memcpy(out, &len, 4);
+        return 0;
+    }
+    case Q3_glBindTransformFeedback:
+        RD(2);
+        glBindTransformFeedback(a[0], qegl3_map_tfo(e, a[1], true));
+        return 0;
+    case Q3_glTransformFeedbackVaryings: {     /* program count mode len names */
+        const GLchar *names[64];
+        RD(3);
+        d = rd_data_len(r, &dlen);
+        if (r->err || a[1] > 64 || (a[1] && !split_names(d, dlen, a[1], names))) {
+            return QEGL_ERR_BAD_CMD;
+        }
+        glTransformFeedbackVaryings(a[0], a[1], names, a[2]);
+        return 0;
+    }
+    case Q3_glGetTransformFeedbackVarying: {   /* program index cap -> len size type name */
+        GLsizei len = 0, size = 0;
+        GLenum type = 0;
+        RD(3);
+        if (a[2] > 4096) return QEGL_ERR_BAD_CMD;
+        out = rd_out(r, 12 + ((a[2] + 3) & ~3u));
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        memset(out, 0, 12 + ((a[2] + 3) & ~3u));
+        glGetTransformFeedbackVarying(a[0], a[1], a[2], &len, &size, &type,
+                                      a[2] ? (GLchar *)out + 12 : NULL);
+        memcpy(out, &len, 4);
+        memcpy(out + 4, &size, 4);
+        memcpy(out + 8, &type, 4);
+        return 0;
+    }
+    case Q3_glFenceSync: {             /* id condition flags */
+        GLsync s, old;
+        RD(3);
+        s = glFenceSync(a[1], a[2]);
+        if (!c->group->sync_map) {
+            c->group->sync_map = g_hash_table_new(g_direct_hash, g_direct_equal);
+        }
+        old = g_hash_table_lookup(c->group->sync_map, GUINT_TO_POINTER(a[0]));
+        if (old) {
+            glDeleteSync(old);
+        }
+        if (s) {
+            g_hash_table_insert(c->group->sync_map, GUINT_TO_POINTER(a[0]), s);
+        } else {
+            g_hash_table_remove(c->group->sync_map, GUINT_TO_POINTER(a[0]));
+        }
+        return 0;
+    }
+    case Q3_glIsSync: {
+        GLsync s;
+        uint32_t v;
+        RD(1);
+        out = rd_out(r, 4);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        s = sync_lookup(e, a[0]);
+        v = s && glIsSync(s);
+        memcpy(out, &v, 4);
+        return 0;
+    }
+    case Q3_glDeleteSync: {
+        GLsync s;
+        RD(1);
+        s = sync_lookup(e, a[0]);
+        if (s) {
+            glDeleteSync(s);
+            g_hash_table_remove(c->group->sync_map, GUINT_TO_POINTER(a[0]));
+        }
+        return 0;
+    }
+    case Q3_glClientWaitSync: {        /* id flags u64 timeout -> status */
+        GLsync s;
+        uint64_t t;
+        uint32_t v;
+        RD(2);
+        t = rd_u64(r);
+        out = rd_out(r, 4);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        s = sync_lookup(e, a[0]);
+        /* never long on the render thread: the guest repeats the wait */
+        v = s ? glClientWaitSync(s, a[1], MIN(t, 5000000)) : GL_WAIT_FAILED;
+        memcpy(out, &v, 4);
+        return 0;
+    }
+    case Q3_glWaitSync: {
+        GLsync s;
+        RD(2);
+        s = sync_lookup(e, a[0]);
+        if (s) {
+            glWaitSync(s, 0, GL_TIMEOUT_IGNORED);
+        }
+        return 0;
+    }
+    case Q3_glGetSynciv: {             /* id pname -> length values[4] */
+        GLsync s;
+        GLint vals[4] = { 0 };
+        GLsizei n = 0;
+        RD(2);
+        out = rd_out(r, 20);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        s = sync_lookup(e, a[0]);
+        if (s) {
+            glGetSynciv(s, a[1], 4, &n, vals);
+        }
+        memcpy(out, &n, 4);
+        memcpy(out + 4, vals, 16);
+        return 0;
+    }
+    case Q3_glGetBufferSubData: {      /* target offset size -> data */
+        RD(3);
+        if (a[2] > (uint32_t)(r->end - r->p)) return QEGL_ERR_BAD_CMD;
+        out = rd_out(r, (a[2] + 3) & ~3u);
+        if (r->err) return QEGL_ERR_BAD_CMD;
+        memset(out, 0, a[2]);
+        if (!bound(binding_of(a[0]))) return QEGL_ERR_GL;
+        glGetBufferSubData(a[0], a[1], a[2], out);
+        return 0;
+    }
+    case Q3_glBindFramebuffer:
+        RD(2);
+        if (a[0] != GL_FRAMEBUFFER && a[0] != GL_DRAW_FRAMEBUFFER &&
+            a[0] != GL_READ_FRAMEBUFFER) {
+            return QEGL_ERR_GL;
+        }
+        bind_framebuffer(e, a[0], a[1]);
+        return 0;
+    case Q3_glInvalidateFramebuffer:
+    case Q3_glInvalidateSubFramebuffer: {  /* target attachments [x y w h] */
+        GLenum att[16];
+        bool sub = index == Q3_glInvalidateSubFramebuffer;
+        GLuint fb;
+        RD(1);
+        d = rd_data_len(r, &dlen);
+        if (r->err || !d || dlen > sizeof(att)) return QEGL_ERR_BAD_CMD;
+        if (sub) {
+            for (i = 1; i < 5; i++) a[i] = rd_u32(r);
+            if (r->err) return QEGL_ERR_BAD_CMD;
+        }
+        memcpy(att, d, dlen);
+        /* only a hint: skipped for the guest's framebuffer 0 (the surface, whose attachment
+           names differ) */
+        fb = a[0] == GL_READ_FRAMEBUFFER ? c->guest_read_fbo : c->guest_fbo;
+        if (fb) {
+            if (sub) {
+                glInvalidateSubFramebuffer(a[0], dlen / 4, att, a[1], a[2], a[3], a[4]);
+            } else {
+                glInvalidateFramebuffer(a[0], dlen / 4, att);
+            }
+        }
+        return 0;
+    }
+    case Q3_glCompressedTexImage2D:
+    case Q3_glCompressedTexImage3D:
+    case Q3_glCompressedTexSubImage2D:
+    case Q3_glCompressedTexSubImage3D: {
+        /* sub dims target level fmt x y z w h d border size, data | 0xfffffffe offset */
+        bool pbo = false;
+        GLint ub = 0;
+        RD(13);
+        if (r->end - r->p >= 4 && *(const uint32_t *)r->p == 0xfffffffeu) {
+            r->p += 4;
+            d = (const void *)(uintptr_t)rd_u32(r);
+            pbo = true;
+            if (r->err) return QEGL_ERR_BAD_CMD;
+            if (!bound(GL_PIXEL_UNPACK_BUFFER_BINDING)) return QEGL_ERR_GL;
+        } else {
+            d = rd_data_len(r, &dlen);
+            if (r->err) return QEGL_ERR_BAD_CMD;
+            if (d && dlen < a[12]) return QEGL_ERR_RANGE;
+            if (!d && a[0]) return QEGL_ERR_BAD_CMD;
+            glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &ub);
+            if (ub) glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+        }
+        if (!a[0] && a[1] == 2) {
+            glCompressedTexImage2D(a[2], a[3], a[4], a[8], a[9], a[11], a[12], d);
+        } else if (!a[0]) {
+            glCompressedTexImage3D(a[2], a[3], a[4], a[8], a[9], a[10], a[11], a[12], d);
+        } else if (a[1] == 2) {
+            glCompressedTexSubImage2D(a[2], a[3], a[5], a[6], a[8], a[9], a[4], a[12], d);
+        } else {
+            glCompressedTexSubImage3D(a[2], a[3], a[5], a[6], a[7], a[8], a[9], a[10], a[4],
+                                      a[12], d);
+        }
+        if (!pbo && ub) glBindBuffer(GL_PIXEL_UNPACK_BUFFER, ub);
+        return 0;
+    }
+    }
+#undef RD
+    return -1;
+}
+
+static int exec_gen3(QEGLExec *e, uint32_t index, Reader r)
+{
+    switch (index) {
+#include "qegl-gl3-gen.inc"
+    }
+    return -1;
+}
+
+int qegl_exec_gl3(QEGLExec *e, uint32_t index, const uint8_t *p, uint32_t len)
+{
+    Reader r = { p, p + len, 0 };
+    int ret;
+    if (!qegl_gl3_ok || index >= Q3_COUNT) {
+        return QEGL_ERR_BAD_CMD;
+    }
+    ret = exec_manual3(e, index, &r);
+    if (ret >= 0) {
+        return ret;
+    }
+    r.p = p;
+    r.err = 0;
+    ret = exec_gen3(e, index, r);
+    if (ret < 0) {
+        if (qegl_debug) {
+            fprintf(stderr, "qegl: unhandled GL3 op %u (%s)\n", index, qegl_gl3_name(index));
+        }
+        return QEGL_ERR_BAD_CMD;
+    }
+    return ret;
+}
+
+/* Which GL3 entry points the host GL has (epoxy aborts on missing ones); the path is offered
+   to the guest (QEGL_FEAT_GL3) only with a GL 3.3+ host that has the ones WebGL 2 needs */
+void qegl_gl3_init(void)
+{
+    HMODULE gl32 = qegl_glfn_on ? qegl_mesa_module : GetModuleHandleA("opengl32.dll");
+    GLint major = 0, minor = 0;
+    int i, n = 0, missing = 0;
+    for (i = 0; i < Q3_COUNT; i++) {
+        const char *nm = qegl3_names[i];
+        PROC pa;
+        if (!strncmp(nm, "qegl", 4)) {
+            qegl3_avail[i] = true;
+            continue;
+        }
+        pa = wglGetProcAddress(nm);
+        if ((uintptr_t)pa > 3 && (intptr_t)pa != -1) {
+            qegl3_avail[i] = true;
+        } else if (gl32 && GetProcAddress(gl32, nm)) {
+            qegl3_avail[i] = true;
+        }
+        if (!qegl3_avail[i]) {
+            missing++;
+            fprintf(stderr, "qegl: GL3 path: host lacks %s\n", nm);
+        }
+        n += qegl3_avail[i];
+    }
+    glGetIntegerv(GL_MAJOR_VERSION, &major);
+    glGetIntegerv(GL_MINOR_VERSION, &minor);
+    qegl_gl3_ok = major * 10 + minor >= 33 && !missing;
+    info_report("qegl: GL3 path %s (host GL %d.%d, %d of %d entry points)",
+                qegl_gl3_ok ? "available" : "unavailable", major, minor, n, Q3_COUNT);
+}
diff --git a/hw/display/qegl-glfn.h b/hw/display/qegl-glfn.h
new file mode 100644
index 0000000..2e34637
--- /dev/null
+++ b/hw/display/qegl-glfn.h
@@ -0,0 +1,2839 @@
+/* Generated by G:\Tiger\qe-vk\gen\gen_glfn.py - do not edit.
+ *
+ * Every GL/WGL entry point qegl calls goes through a per-process switch: libepoxy
+ * (system opengl32, the native driver) unless qegl_glfn_on is set, in which case the
+ * table qegl_glfn[] (filled from a private Mesa opengl32.dll: backend=zink|d3d12|llvmpipe)
+ * is used. */
+#ifndef HW_DISPLAY_QEGL_GLFN_H
+#define HW_DISPLAY_QEGL_GLFN_H
+
+enum {
+    QGLFN_glAccum,
+    QGLFN_glActiveTexture,
+    QGLFN_glAlphaFunc,
+    QGLFN_glAreTexturesResident,
+    QGLFN_glAttachObjectARB,
+    QGLFN_glBegin,
+    QGLFN_glBeginQuery,
+    QGLFN_glBeginTransformFeedback,
+    QGLFN_glBindAttribLocationARB,
+    QGLFN_glBindBuffer,
+    QGLFN_glBindBufferBase,
+    QGLFN_glBindBufferRange,
+    QGLFN_glBindFragDataLocation,
+    QGLFN_glBindFramebuffer,
+    QGLFN_glBindFramebufferEXT,
+    QGLFN_glBindProgramARB,
+    QGLFN_glBindRenderbufferEXT,
+    QGLFN_glBindSampler,
+    QGLFN_glBindTexture,
+    QGLFN_glBindTransformFeedback,
+    QGLFN_glBindVertexArray,
+    QGLFN_glBitmap,
+    QGLFN_glBlendColor,
+    QGLFN_glBlendEquation,
+    QGLFN_glBlendEquationSeparate,
+    QGLFN_glBlendFunc,
+    QGLFN_glBlendFuncSeparate,
+    QGLFN_glBlitFramebuffer,
+    QGLFN_glBufferData,
+    QGLFN_glBufferSubData,
+    QGLFN_glCallList,
+    QGLFN_glCallLists,
+    QGLFN_glCheckFramebufferStatus,
+    QGLFN_glCheckFramebufferStatusEXT,
+    QGLFN_glClear,
+    QGLFN_glClearAccum,
+    QGLFN_glClearBufferfi,
+    QGLFN_glClearBufferfv,
+    QGLFN_glClearBufferiv,
+    QGLFN_glClearBufferuiv,
+    QGLFN_glClearColor,
+    QGLFN_glClearDepth,
+    QGLFN_glClearIndex,
+    QGLFN_glClearStencil,
+    QGLFN_glClientActiveTexture,
+    QGLFN_glClientWaitSync,
+    QGLFN_glClipPlane,
+    QGLFN_glColor3b,
+    QGLFN_glColor3bv,
+    QGLFN_glColor3d,
+    QGLFN_glColor3dv,
+    QGLFN_glColor3f,
+    QGLFN_glColor3fv,
+    QGLFN_glColor3i,
+    QGLFN_glColor3iv,
+    QGLFN_glColor3s,
+    QGLFN_glColor3sv,
+    QGLFN_glColor3ub,
+    QGLFN_glColor3ubv,
+    QGLFN_glColor3ui,
+    QGLFN_glColor3uiv,
+    QGLFN_glColor3us,
+    QGLFN_glColor3usv,
+    QGLFN_glColor4b,
+    QGLFN_glColor4bv,
+    QGLFN_glColor4d,
+    QGLFN_glColor4dv,
+    QGLFN_glColor4f,
+    QGLFN_glColor4fv,
+    QGLFN_glColor4i,
+    QGLFN_glColor4iv,
+    QGLFN_glColor4s,
+    QGLFN_glColor4sv,
+    QGLFN_glColor4ub,
+    QGLFN_glColor4ubv,
+    QGLFN_glColor4ui,
+    QGLFN_glColor4uiv,
+    QGLFN_glColor4us,
+    QGLFN_glColor4usv,
+    QGLFN_glColorMask,
+    QGLFN_glColorMaski,
+    QGLFN_glColorMaterial,
+    QGLFN_glColorPointer,
+    QGLFN_glColorTableParameterfv,
+    QGLFN_glColorTableParameteriv,
+    QGLFN_glCombinerInputNV,
+    QGLFN_glCombinerOutputNV,
+    QGLFN_glCombinerParameterfNV,
+    QGLFN_glCombinerParameterfvNV,
+    QGLFN_glCombinerParameteriNV,
+    QGLFN_glCombinerParameterivNV,
+    QGLFN_glCombinerStageParameterfvNV,
+    QGLFN_glCompileShaderARB,
+    QGLFN_glCompressedTexImage2D,
+    QGLFN_glCompressedTexImage3D,
+    QGLFN_glCompressedTexSubImage2D,
+    QGLFN_glCompressedTexSubImage3D,
+    QGLFN_glConvolutionParameterf,
+    QGLFN_glConvolutionParameterfv,
+    QGLFN_glConvolutionParameteri,
+    QGLFN_glConvolutionParameteriv,
+    QGLFN_glCopyBufferSubData,
+    QGLFN_glCopyColorSubTable,
+    QGLFN_glCopyColorTable,
+    QGLFN_glCopyConvolutionFilter1D,
+    QGLFN_glCopyConvolutionFilter2D,
+    QGLFN_glCopyImageSubData,
+    QGLFN_glCopyPixels,
+    QGLFN_glCopyTexImage1D,
+    QGLFN_glCopyTexImage2D,
+    QGLFN_glCopyTexSubImage1D,
+    QGLFN_glCopyTexSubImage2D,
+    QGLFN_glCopyTexSubImage3D,
+    QGLFN_glCreateMemoryObjectsEXT,
+    QGLFN_glCreateProgramObjectARB,
+    QGLFN_glCreateShaderObjectARB,
+    QGLFN_glCullFace,
+    QGLFN_glDebugMessageCallback,
+    QGLFN_glDeleteBuffers,
+    QGLFN_glDeleteFramebuffers,
+    QGLFN_glDeleteFramebuffersEXT,
+    QGLFN_glDeleteLists,
+    QGLFN_glDeleteMemoryObjectsEXT,
+    QGLFN_glDeleteObjectARB,
+    QGLFN_glDeleteProgramsARB,
+    QGLFN_glDeleteQueries,
+    QGLFN_glDeleteRenderbuffers,
+    QGLFN_glDeleteRenderbuffersEXT,
+    QGLFN_glDeleteSamplers,
+    QGLFN_glDeleteSemaphoresEXT,
+    QGLFN_glDeleteSync,
+    QGLFN_glDeleteTextures,
+    QGLFN_glDeleteTransformFeedbacks,
+    QGLFN_glDeleteVertexArrays,
+    QGLFN_glDepthBoundsEXT,
+    QGLFN_glDepthFunc,
+    QGLFN_glDepthMask,
+    QGLFN_glDepthRange,
+    QGLFN_glDetachObjectARB,
+    QGLFN_glDisable,
+    QGLFN_glDisableClientState,
+    QGLFN_glDisableVertexAttribArrayARB,
+    QGLFN_glDisablei,
+    QGLFN_glDrawArrays,
+    QGLFN_glDrawArraysInstanced,
+    QGLFN_glDrawBuffer,
+    QGLFN_glDrawBuffers,
+    QGLFN_glDrawElementsInstanced,
+    QGLFN_glDrawPixels,
+    QGLFN_glDrawRangeElements,
+    QGLFN_glEdgeFlag,
+    QGLFN_glEdgeFlagPointer,
+    QGLFN_glEdgeFlagv,
+    QGLFN_glEnable,
+    QGLFN_glEnableClientState,
+    QGLFN_glEnableVertexAttribArrayARB,
+    QGLFN_glEnablei,
+    QGLFN_glEnd,
+    QGLFN_glEndList,
+    QGLFN_glEndQuery,
+    QGLFN_glEndTransformFeedback,
+    QGLFN_glEvalCoord1d,
+    QGLFN_glEvalCoord1dv,
+    QGLFN_glEvalCoord1f,
+    QGLFN_glEvalCoord1fv,
+    QGLFN_glEvalCoord2d,
+    QGLFN_glEvalCoord2dv,
+    QGLFN_glEvalCoord2f,
+    QGLFN_glEvalCoord2fv,
+    QGLFN_glEvalMesh1,
+    QGLFN_glEvalMesh2,
+    QGLFN_glEvalPoint1,
+    QGLFN_glEvalPoint2,
+    QGLFN_glFenceSync,
+    QGLFN_glFinalCombinerInputNV,
+    QGLFN_glFinish,
+    QGLFN_glFlush,
+    QGLFN_glFogCoordPointer,
+    QGLFN_glFogCoordd,
+    QGLFN_glFogCoorddv,
+    QGLFN_glFogCoordf,
+    QGLFN_glFogCoordfv,
+    QGLFN_glFogf,
+    QGLFN_glFogfv,
+    QGLFN_glFogi,
+    QGLFN_glFogiv,
+    QGLFN_glFramebufferRenderbuffer,
+    QGLFN_glFramebufferRenderbufferEXT,
+    QGLFN_glFramebufferTexture1DEXT,
+    QGLFN_glFramebufferTexture2D,
+    QGLFN_glFramebufferTexture2DEXT,
+    QGLFN_glFramebufferTexture3DEXT,
+    QGLFN_glFramebufferTextureLayer,
+    QGLFN_glFrontFace,
+    QGLFN_glFrustum,
+    QGLFN_glGenFramebuffers,
+    QGLFN_glGenSamplers,
+    QGLFN_glGenSemaphoresEXT,
+    QGLFN_glGenTransformFeedbacks,
+    QGLFN_glGenVertexArrays,
+    QGLFN_glGenerateMipmapEXT,
+    QGLFN_glGetActiveAttribARB,
+    QGLFN_glGetActiveUniformARB,
+    QGLFN_glGetActiveUniformBlockName,
+    QGLFN_glGetActiveUniformBlockiv,
+    QGLFN_glGetActiveUniformsiv,
+    QGLFN_glGetAttachedObjectsARB,
+    QGLFN_glGetAttribLocationARB,
+    QGLFN_glGetBooleani_v,
+    QGLFN_glGetBooleanv,
+    QGLFN_glGetBufferParameteri64v,
+    QGLFN_glGetBufferParameteriv,
+    QGLFN_glGetBufferSubData,
+    QGLFN_glGetClipPlane,
+    QGLFN_glGetColorTableParameterfv,
+    QGLFN_glGetColorTableParameteriv,
+    QGLFN_glGetCombinerInputParameterfvNV,
+    QGLFN_glGetCombinerInputParameterivNV,
+    QGLFN_glGetCombinerOutputParameterfvNV,
+    QGLFN_glGetCombinerOutputParameterivNV,
+    QGLFN_glGetCombinerStageParameterfvNV,
+    QGLFN_glGetConvolutionParameterfv,
+    QGLFN_glGetConvolutionParameteriv,
+    QGLFN_glGetDoublev,
+    QGLFN_glGetError,
+    QGLFN_glGetFinalCombinerInputParameterfvNV,
+    QGLFN_glGetFinalCombinerInputParameterivNV,
+    QGLFN_glGetFloatv,
+    QGLFN_glGetFragDataLocation,
+    QGLFN_glGetFramebufferAttachmentParameteriv,
+    QGLFN_glGetFramebufferAttachmentParameterivEXT,
+    QGLFN_glGetHistogramParameterfv,
+    QGLFN_glGetHistogramParameteriv,
+    QGLFN_glGetInfoLogARB,
+    QGLFN_glGetInteger64i_v,
+    QGLFN_glGetInteger64v,
+    QGLFN_glGetIntegeri_v,
+    QGLFN_glGetIntegerv,
+    QGLFN_glGetInternalformativ,
+    QGLFN_glGetLightfv,
+    QGLFN_glGetLightiv,
+    QGLFN_glGetMaterialfv,
+    QGLFN_glGetMaterialiv,
+    QGLFN_glGetMinmaxParameterfv,
+    QGLFN_glGetMinmaxParameteriv,
+    QGLFN_glGetNamedFramebufferAttachmentParameteriv,
+    QGLFN_glGetObjectParameterfvARB,
+    QGLFN_glGetObjectParameterivARB,
+    QGLFN_glGetProgramEnvParameterdvARB,
+    QGLFN_glGetProgramEnvParameterfvARB,
+    QGLFN_glGetProgramInfoLog,
+    QGLFN_glGetProgramLocalParameterdvARB,
+    QGLFN_glGetProgramLocalParameterfvARB,
+    QGLFN_glGetProgramiv,
+    QGLFN_glGetProgramivARB,
+    QGLFN_glGetQueryObjecti64v,
+    QGLFN_glGetQueryObjectiv,
+    QGLFN_glGetQueryObjectui64v,
+    QGLFN_glGetQueryObjectuiv,
+    QGLFN_glGetQueryiv,
+    QGLFN_glGetRenderbufferParameterivEXT,
+    QGLFN_glGetSamplerParameterfv,
+    QGLFN_glGetSamplerParameteriv,
+    QGLFN_glGetShaderInfoLog,
+    QGLFN_glGetShaderSourceARB,
+    QGLFN_glGetShaderiv,
+    QGLFN_glGetString,
+    QGLFN_glGetStringi,
+    QGLFN_glGetSynciv,
+    QGLFN_glGetTexEnvfv,
+    QGLFN_glGetTexEnviv,
+    QGLFN_glGetTexGendv,
+    QGLFN_glGetTexGenfv,
+    QGLFN_glGetTexGeniv,
+    QGLFN_glGetTexLevelParameterfv,
+    QGLFN_glGetTexLevelParameteriv,
+    QGLFN_glGetTexParameterfv,
+    QGLFN_glGetTexParameteriv,
+    QGLFN_glGetTransformFeedbackVarying,
+    QGLFN_glGetUniformBlockIndex,
+    QGLFN_glGetUniformIndices,
+    QGLFN_glGetUniformLocationARB,
+    QGLFN_glGetUniformfvARB,
+    QGLFN_glGetUniformivARB,
+    QGLFN_glGetUniformuiv,
+    QGLFN_glGetUnsignedBytevEXT,
+    QGLFN_glGetVertexAttribIiv,
+    QGLFN_glGetVertexAttribIuiv,
+    QGLFN_glGetVertexAttribdvARB,
+    QGLFN_glGetVertexAttribfvARB,
+    QGLFN_glGetVertexAttribivARB,
+    QGLFN_glHint,
+    QGLFN_glHistogram,
+    QGLFN_glImportMemoryWin32HandleEXT,
+    QGLFN_glImportSemaphoreWin32HandleEXT,
+    QGLFN_glIndexMask,
+    QGLFN_glIndexPointer,
+    QGLFN_glIndexd,
+    QGLFN_glIndexdv,
+    QGLFN_glIndexf,
+    QGLFN_glIndexfv,
+    QGLFN_glIndexi,
+    QGLFN_glIndexiv,
+    QGLFN_glIndexs,
+    QGLFN_glIndexsv,
+    QGLFN_glIndexub,
+    QGLFN_glIndexubv,
+    QGLFN_glInitNames,
+    QGLFN_glInvalidateFramebuffer,
+    QGLFN_glInvalidateSubFramebuffer,
+    QGLFN_glIsBuffer,
+    QGLFN_glIsEnabled,
+    QGLFN_glIsEnabledi,
+    QGLFN_glIsFramebufferEXT,
+    QGLFN_glIsList,
+    QGLFN_glIsProgram,
+    QGLFN_glIsProgramARB,
+    QGLFN_glIsQuery,
+    QGLFN_glIsRenderbufferEXT,
+    QGLFN_glIsSampler,
+    QGLFN_glIsShader,
+    QGLFN_glIsSync,
+    QGLFN_glIsTexture,
+    QGLFN_glLightModelf,
+    QGLFN_glLightModelfv,
+    QGLFN_glLightModeli,
+    QGLFN_glLightModeliv,
+    QGLFN_glLightf,
+    QGLFN_glLightfv,
+    QGLFN_glLighti,
+    QGLFN_glLightiv,
+    QGLFN_glLineStipple,
+    QGLFN_glLineWidth,
+    QGLFN_glLinkProgramARB,
+    QGLFN_glListBase,
+    QGLFN_glLoadIdentity,
+    QGLFN_glLoadMatrixd,
+    QGLFN_glLoadMatrixf,
+    QGLFN_glLoadName,
+    QGLFN_glLoadTransposeMatrixd,
+    QGLFN_glLoadTransposeMatrixf,
+    QGLFN_glLogicOp,
+    QGLFN_glMapBufferRange,
+    QGLFN_glMapGrid1d,
+    QGLFN_glMapGrid1f,
+    QGLFN_glMapGrid2d,
+    QGLFN_glMapGrid2f,
+    QGLFN_glMaterialf,
+    QGLFN_glMaterialfv,
+    QGLFN_glMateriali,
+    QGLFN_glMaterialiv,
+    QGLFN_glMatrixMode,
+    QGLFN_glMemoryObjectParameterivEXT,
+    QGLFN_glMinmax,
+    QGLFN_glMultMatrixd,
+    QGLFN_glMultMatrixf,
+    QGLFN_glMultTransposeMatrixd,
+    QGLFN_glMultTransposeMatrixf,
+    QGLFN_glMultiTexCoord1d,
+    QGLFN_glMultiTexCoord1dv,
+    QGLFN_glMultiTexCoord1f,
+    QGLFN_glMultiTexCoord1fv,
+    QGLFN_glMultiTexCoord1i,
+    QGLFN_glMultiTexCoord1iv,
+    QGLFN_glMultiTexCoord1s,
+    QGLFN_glMultiTexCoord1sv,
+    QGLFN_glMultiTexCoord2d,
+    QGLFN_glMultiTexCoord2dv,
+    QGLFN_glMultiTexCoord2f,
+    QGLFN_glMultiTexCoord2fv,
+    QGLFN_glMultiTexCoord2i,
+    QGLFN_glMultiTexCoord2iv,
+    QGLFN_glMultiTexCoord2s,
+    QGLFN_glMultiTexCoord2sv,
+    QGLFN_glMultiTexCoord3d,
+    QGLFN_glMultiTexCoord3dv,
+    QGLFN_glMultiTexCoord3f,
+    QGLFN_glMultiTexCoord3fv,
+    QGLFN_glMultiTexCoord3i,
+    QGLFN_glMultiTexCoord3iv,
+    QGLFN_glMultiTexCoord3s,
+    QGLFN_glMultiTexCoord3sv,
+    QGLFN_glMultiTexCoord4d,
+    QGLFN_glMultiTexCoord4dv,
+    QGLFN_glMultiTexCoord4f,
+    QGLFN_glMultiTexCoord4fv,
+    QGLFN_glMultiTexCoord4i,
+    QGLFN_glMultiTexCoord4iv,
+    QGLFN_glMultiTexCoord4s,
+    QGLFN_glMultiTexCoord4sv,
+    QGLFN_glNewList,
+    QGLFN_glNormal3b,
+    QGLFN_glNormal3bv,
+    QGLFN_glNormal3d,
+    QGLFN_glNormal3dv,
+    QGLFN_glNormal3f,
+    QGLFN_glNormal3fv,
+    QGLFN_glNormal3i,
+    QGLFN_glNormal3iv,
+    QGLFN_glNormal3s,
+    QGLFN_glNormal3sv,
+    QGLFN_glNormalPointer,
+    QGLFN_glOrtho,
+    QGLFN_glPassThrough,
+    QGLFN_glPauseTransformFeedback,
+    QGLFN_glPixelStorei,
+    QGLFN_glPixelTransferf,
+    QGLFN_glPixelTransferi,
+    QGLFN_glPixelZoom,
+    QGLFN_glPointParameterf,
+    QGLFN_glPointParameterfv,
+    QGLFN_glPointParameteri,
+    QGLFN_glPointParameteriv,
+    QGLFN_glPointSize,
+    QGLFN_glPolygonMode,
+    QGLFN_glPolygonOffset,
+    QGLFN_glPolygonStipple,
+    QGLFN_glPopAttrib,
+    QGLFN_glPopMatrix,
+    QGLFN_glPopName,
+    QGLFN_glPrimitiveRestartIndex,
+    QGLFN_glPrioritizeTextures,
+    QGLFN_glProgramEnvParameter4dARB,
+    QGLFN_glProgramEnvParameter4dvARB,
+    QGLFN_glProgramEnvParameter4fARB,
+    QGLFN_glProgramEnvParameter4fvARB,
+    QGLFN_glProgramEnvParameters4fvEXT,
+    QGLFN_glProgramLocalParameter4dARB,
+    QGLFN_glProgramLocalParameter4dvARB,
+    QGLFN_glProgramLocalParameter4fARB,
+    QGLFN_glProgramLocalParameter4fvARB,
+    QGLFN_glProgramLocalParameters4fvEXT,
+    QGLFN_glProgramStringARB,
+    QGLFN_glPushAttrib,
+    QGLFN_glPushMatrix,
+    QGLFN_glPushName,
+    QGLFN_glQueryCounter,
+    QGLFN_glRasterPos2d,
+    QGLFN_glRasterPos2dv,
+    QGLFN_glRasterPos2f,
+    QGLFN_glRasterPos2fv,
+    QGLFN_glRasterPos2i,
+    QGLFN_glRasterPos2iv,
+    QGLFN_glRasterPos2s,
+    QGLFN_glRasterPos2sv,
+    QGLFN_glRasterPos3d,
+    QGLFN_glRasterPos3dv,
+    QGLFN_glRasterPos3f,
+    QGLFN_glRasterPos3fv,
+    QGLFN_glRasterPos3i,
+    QGLFN_glRasterPos3iv,
+    QGLFN_glRasterPos3s,
+    QGLFN_glRasterPos3sv,
+    QGLFN_glRasterPos4d,
+    QGLFN_glRasterPos4dv,
+    QGLFN_glRasterPos4f,
+    QGLFN_glRasterPos4fv,
+    QGLFN_glRasterPos4i,
+    QGLFN_glRasterPos4iv,
+    QGLFN_glRasterPos4s,
+    QGLFN_glRasterPos4sv,
+    QGLFN_glReadBuffer,
+    QGLFN_glReadPixels,
+    QGLFN_glRectd,
+    QGLFN_glRectdv,
+    QGLFN_glRectf,
+    QGLFN_glRectfv,
+    QGLFN_glRecti,
+    QGLFN_glRectiv,
+    QGLFN_glRects,
+    QGLFN_glRectsv,
+    QGLFN_glRenderMode,
+    QGLFN_glRenderbufferStorage,
+    QGLFN_glRenderbufferStorageEXT,
+    QGLFN_glRenderbufferStorageMultisample,
+    QGLFN_glResetHistogram,
+    QGLFN_glResetMinmax,
+    QGLFN_glResumeTransformFeedback,
+    QGLFN_glRotated,
+    QGLFN_glRotatef,
+    QGLFN_glSampleCoverage,
+    QGLFN_glSamplerParameterf,
+    QGLFN_glSamplerParameterfv,
+    QGLFN_glSamplerParameteri,
+    QGLFN_glSamplerParameteriv,
+    QGLFN_glScaled,
+    QGLFN_glScalef,
+    QGLFN_glScissor,
+    QGLFN_glSecondaryColor3b,
+    QGLFN_glSecondaryColor3bv,
+    QGLFN_glSecondaryColor3d,
+    QGLFN_glSecondaryColor3dv,
+    QGLFN_glSecondaryColor3f,
+    QGLFN_glSecondaryColor3fv,
+    QGLFN_glSecondaryColor3i,
+    QGLFN_glSecondaryColor3iv,
+    QGLFN_glSecondaryColor3s,
+    QGLFN_glSecondaryColor3sv,
+    QGLFN_glSecondaryColor3ub,
+    QGLFN_glSecondaryColor3ubv,
+    QGLFN_glSecondaryColor3ui,
+    QGLFN_glSecondaryColor3uiv,
+    QGLFN_glSecondaryColor3us,
+    QGLFN_glSecondaryColor3usv,
+    QGLFN_glSecondaryColorPointer,
+    QGLFN_glSemaphoreParameterui64vEXT,
+    QGLFN_glShadeModel,
+    QGLFN_glShaderSourceARB,
+    QGLFN_glSignalSemaphoreEXT,
+    QGLFN_glStencilFunc,
+    QGLFN_glStencilFuncSeparate,
+    QGLFN_glStencilMask,
+    QGLFN_glStencilMaskSeparate,
+    QGLFN_glStencilOp,
+    QGLFN_glStencilOpSeparate,
+    QGLFN_glTexCoord1d,
+    QGLFN_glTexCoord1dv,
+    QGLFN_glTexCoord1f,
+    QGLFN_glTexCoord1fv,
+    QGLFN_glTexCoord1i,
+    QGLFN_glTexCoord1iv,
+    QGLFN_glTexCoord1s,
+    QGLFN_glTexCoord1sv,
+    QGLFN_glTexCoord2d,
+    QGLFN_glTexCoord2dv,
+    QGLFN_glTexCoord2f,
+    QGLFN_glTexCoord2fv,
+    QGLFN_glTexCoord2i,
+    QGLFN_glTexCoord2iv,
+    QGLFN_glTexCoord2s,
+    QGLFN_glTexCoord2sv,
+    QGLFN_glTexCoord3d,
+    QGLFN_glTexCoord3dv,
+    QGLFN_glTexCoord3f,
+    QGLFN_glTexCoord3fv,
+    QGLFN_glTexCoord3i,
+    QGLFN_glTexCoord3iv,
+    QGLFN_glTexCoord3s,
+    QGLFN_glTexCoord3sv,
+    QGLFN_glTexCoord4d,
+    QGLFN_glTexCoord4dv,
+    QGLFN_glTexCoord4f,
+    QGLFN_glTexCoord4fv,
+    QGLFN_glTexCoord4i,
+    QGLFN_glTexCoord4iv,
+    QGLFN_glTexCoord4s,
+    QGLFN_glTexCoord4sv,
+    QGLFN_glTexCoordPointer,
+    QGLFN_glTexEnvf,
+    QGLFN_glTexEnvfv,
+    QGLFN_glTexEnvi,
+    QGLFN_glTexEnviv,
+    QGLFN_glTexGend,
+    QGLFN_glTexGendv,
+    QGLFN_glTexGenf,
+    QGLFN_glTexGenfv,
+    QGLFN_glTexGeni,
+    QGLFN_glTexGeniv,
+    QGLFN_glTexImage1D,
+    QGLFN_glTexImage2D,
+    QGLFN_glTexImage3D,
+    QGLFN_glTexParameterf,
+    QGLFN_glTexParameterfv,
+    QGLFN_glTexParameteri,
+    QGLFN_glTexParameteriv,
+    QGLFN_glTexStorage2D,
+    QGLFN_glTexStorage3D,
+    QGLFN_glTexStorageMem2DEXT,
+    QGLFN_glTexSubImage1D,
+    QGLFN_glTexSubImage2D,
+    QGLFN_glTexSubImage3D,
+    QGLFN_glTransformFeedbackVaryings,
+    QGLFN_glTranslated,
+    QGLFN_glTranslatef,
+    QGLFN_glUniform1fARB,
+    QGLFN_glUniform1fvARB,
+    QGLFN_glUniform1iARB,
+    QGLFN_glUniform1ivARB,
+    QGLFN_glUniform1ui,
+    QGLFN_glUniform1uiv,
+    QGLFN_glUniform2fARB,
+    QGLFN_glUniform2fvARB,
+    QGLFN_glUniform2iARB,
+    QGLFN_glUniform2ivARB,
+    QGLFN_glUniform2ui,
+    QGLFN_glUniform2uiv,
+    QGLFN_glUniform3fARB,
+    QGLFN_glUniform3fvARB,
+    QGLFN_glUniform3iARB,
+    QGLFN_glUniform3ivARB,
+    QGLFN_glUniform3ui,
+    QGLFN_glUniform3uiv,
+    QGLFN_glUniform4fARB,
+    QGLFN_glUniform4fvARB,
+    QGLFN_glUniform4iARB,
+    QGLFN_glUniform4ivARB,
+    QGLFN_glUniform4ui,
+    QGLFN_glUniform4uiv,
+    QGLFN_glUniformBlockBinding,
+    QGLFN_glUniformMatrix2fvARB,
+    QGLFN_glUniformMatrix2x3fv,
+    QGLFN_glUniformMatrix2x4fv,
+    QGLFN_glUniformMatrix3fvARB,
+    QGLFN_glUniformMatrix3x2fv,
+    QGLFN_glUniformMatrix3x4fv,
+    QGLFN_glUniformMatrix4fvARB,
+    QGLFN_glUniformMatrix4x2fv,
+    QGLFN_glUniformMatrix4x3fv,
+    QGLFN_glUnmapBuffer,
+    QGLFN_glUseProgramObjectARB,
+    QGLFN_glValidateProgramARB,
+    QGLFN_glVertex2d,
+    QGLFN_glVertex2dv,
+    QGLFN_glVertex2f,
+    QGLFN_glVertex2fv,
+    QGLFN_glVertex2i,
+    QGLFN_glVertex2iv,
+    QGLFN_glVertex2s,
+    QGLFN_glVertex2sv,
+    QGLFN_glVertex3d,
+    QGLFN_glVertex3dv,
+    QGLFN_glVertex3f,
+    QGLFN_glVertex3fv,
+    QGLFN_glVertex3i,
+    QGLFN_glVertex3iv,
+    QGLFN_glVertex3s,
+    QGLFN_glVertex3sv,
+    QGLFN_glVertex4d,
+    QGLFN_glVertex4dv,
+    QGLFN_glVertex4f,
+    QGLFN_glVertex4fv,
+    QGLFN_glVertex4i,
+    QGLFN_glVertex4iv,
+    QGLFN_glVertex4s,
+    QGLFN_glVertex4sv,
+    QGLFN_glVertexAttrib1dARB,
+    QGLFN_glVertexAttrib1dvARB,
+    QGLFN_glVertexAttrib1fARB,
+    QGLFN_glVertexAttrib1fvARB,
+    QGLFN_glVertexAttrib1sARB,
+    QGLFN_glVertexAttrib1svARB,
+    QGLFN_glVertexAttrib2dARB,
+    QGLFN_glVertexAttrib2dvARB,
+    QGLFN_glVertexAttrib2fARB,
+    QGLFN_glVertexAttrib2fvARB,
+    QGLFN_glVertexAttrib2sARB,
+    QGLFN_glVertexAttrib2svARB,
+    QGLFN_glVertexAttrib3dARB,
+    QGLFN_glVertexAttrib3dvARB,
+    QGLFN_glVertexAttrib3fARB,
+    QGLFN_glVertexAttrib3fvARB,
+    QGLFN_glVertexAttrib3sARB,
+    QGLFN_glVertexAttrib3svARB,
+    QGLFN_glVertexAttrib4NbvARB,
+    QGLFN_glVertexAttrib4NivARB,
+    QGLFN_glVertexAttrib4NsvARB,
+    QGLFN_glVertexAttrib4NubARB,
+    QGLFN_glVertexAttrib4NubvARB,
+    QGLFN_glVertexAttrib4NuivARB,
+    QGLFN_glVertexAttrib4NusvARB,
+    QGLFN_glVertexAttrib4bvARB,
+    QGLFN_glVertexAttrib4dARB,
+    QGLFN_glVertexAttrib4dvARB,
+    QGLFN_glVertexAttrib4fARB,
+    QGLFN_glVertexAttrib4fvARB,
+    QGLFN_glVertexAttrib4ivARB,
+    QGLFN_glVertexAttrib4sARB,
+    QGLFN_glVertexAttrib4svARB,
+    QGLFN_glVertexAttrib4ubvARB,
+    QGLFN_glVertexAttrib4uivARB,
+    QGLFN_glVertexAttrib4usvARB,
+    QGLFN_glVertexAttribDivisor,
+    QGLFN_glVertexAttribI4i,
+    QGLFN_glVertexAttribI4iv,
+    QGLFN_glVertexAttribI4ui,
+    QGLFN_glVertexAttribI4uiv,
+    QGLFN_glVertexAttribIPointer,
+    QGLFN_glVertexAttribPointerARB,
+    QGLFN_glVertexPointer,
+    QGLFN_glViewport,
+    QGLFN_glWaitSync,
+    QGLFN_glWindowPos2d,
+    QGLFN_glWindowPos2dv,
+    QGLFN_glWindowPos2f,
+    QGLFN_glWindowPos2fv,
+    QGLFN_glWindowPos2i,
+    QGLFN_glWindowPos2iv,
+    QGLFN_glWindowPos2s,
+    QGLFN_glWindowPos2sv,
+    QGLFN_glWindowPos3d,
+    QGLFN_glWindowPos3dv,
+    QGLFN_glWindowPos3f,
+    QGLFN_glWindowPos3fv,
+    QGLFN_glWindowPos3i,
+    QGLFN_glWindowPos3iv,
+    QGLFN_glWindowPos3s,
+    QGLFN_glWindowPos3sv,
+    QGLFN_wglCreateContext,
+    QGLFN_wglCreateContextAttribsARB,
+    QGLFN_wglDeleteContext,
+    QGLFN_wglGetProcAddress,
+    QGLFN_wglMakeCurrent,
+    QGLFN_wglShareLists,
+    QEGL_GLFN_COUNT
+};
+
+extern void *qegl_glfn[QEGL_GLFN_COUNT];
+extern const char *const qegl_glfn_names[QEGL_GLFN_COUNT];
+extern bool qegl_glfn_on;
+
+#define QEGL_FN(name) (__builtin_expect(qegl_glfn_on, 0) ? \
+    (__typeof__(epoxy_##name))qegl_glfn[QGLFN_##name] : epoxy_##name)
+
+#undef glAccum
+#define glAccum QEGL_FN(glAccum)
+#undef glActiveTexture
+#define glActiveTexture QEGL_FN(glActiveTexture)
+#undef glAlphaFunc
+#define glAlphaFunc QEGL_FN(glAlphaFunc)
+#undef glAreTexturesResident
+#define glAreTexturesResident QEGL_FN(glAreTexturesResident)
+#undef glAttachObjectARB
+#define glAttachObjectARB QEGL_FN(glAttachObjectARB)
+#undef glBegin
+#define glBegin QEGL_FN(glBegin)
+#undef glBeginQuery
+#define glBeginQuery QEGL_FN(glBeginQuery)
+#undef glBeginTransformFeedback
+#define glBeginTransformFeedback QEGL_FN(glBeginTransformFeedback)
+#undef glBindAttribLocationARB
+#define glBindAttribLocationARB QEGL_FN(glBindAttribLocationARB)
+#undef glBindBuffer
+#define glBindBuffer QEGL_FN(glBindBuffer)
+#undef glBindBufferBase
+#define glBindBufferBase QEGL_FN(glBindBufferBase)
+#undef glBindBufferRange
+#define glBindBufferRange QEGL_FN(glBindBufferRange)
+#undef glBindFragDataLocation
+#define glBindFragDataLocation QEGL_FN(glBindFragDataLocation)
+#undef glBindFramebuffer
+#define glBindFramebuffer QEGL_FN(glBindFramebuffer)
+#undef glBindFramebufferEXT
+#define glBindFramebufferEXT QEGL_FN(glBindFramebufferEXT)
+#undef glBindProgramARB
+#define glBindProgramARB QEGL_FN(glBindProgramARB)
+#undef glBindRenderbufferEXT
+#define glBindRenderbufferEXT QEGL_FN(glBindRenderbufferEXT)
+#undef glBindSampler
+#define glBindSampler QEGL_FN(glBindSampler)
+#undef glBindTexture
+#define glBindTexture QEGL_FN(glBindTexture)
+#undef glBindTransformFeedback
+#define glBindTransformFeedback QEGL_FN(glBindTransformFeedback)
+#undef glBindVertexArray
+#define glBindVertexArray QEGL_FN(glBindVertexArray)
+#undef glBitmap
+#define glBitmap QEGL_FN(glBitmap)
+#undef glBlendColor
+#define glBlendColor QEGL_FN(glBlendColor)
+#undef glBlendEquation
+#define glBlendEquation QEGL_FN(glBlendEquation)
+#undef glBlendEquationSeparate
+#define glBlendEquationSeparate QEGL_FN(glBlendEquationSeparate)
+#undef glBlendFunc
+#define glBlendFunc QEGL_FN(glBlendFunc)
+#undef glBlendFuncSeparate
+#define glBlendFuncSeparate QEGL_FN(glBlendFuncSeparate)
+#undef glBlitFramebuffer
+#define glBlitFramebuffer QEGL_FN(glBlitFramebuffer)
+#undef glBufferData
+#define glBufferData QEGL_FN(glBufferData)
+#undef glBufferSubData
+#define glBufferSubData QEGL_FN(glBufferSubData)
+#undef glCallList
+#define glCallList QEGL_FN(glCallList)
+#undef glCallLists
+#define glCallLists QEGL_FN(glCallLists)
+#undef glCheckFramebufferStatus
+#define glCheckFramebufferStatus QEGL_FN(glCheckFramebufferStatus)
+#undef glCheckFramebufferStatusEXT
+#define glCheckFramebufferStatusEXT QEGL_FN(glCheckFramebufferStatusEXT)
+#undef glClear
+#define glClear QEGL_FN(glClear)
+#undef glClearAccum
+#define glClearAccum QEGL_FN(glClearAccum)
+#undef glClearBufferfi
+#define glClearBufferfi QEGL_FN(glClearBufferfi)
+#undef glClearBufferfv
+#define glClearBufferfv QEGL_FN(glClearBufferfv)
+#undef glClearBufferiv
+#define glClearBufferiv QEGL_FN(glClearBufferiv)
+#undef glClearBufferuiv
+#define glClearBufferuiv QEGL_FN(glClearBufferuiv)
+#undef glClearColor
+#define glClearColor QEGL_FN(glClearColor)
+#undef glClearDepth
+#define glClearDepth QEGL_FN(glClearDepth)
+#undef glClearIndex
+#define glClearIndex QEGL_FN(glClearIndex)
+#undef glClearStencil
+#define glClearStencil QEGL_FN(glClearStencil)
+#undef glClientActiveTexture
+#define glClientActiveTexture QEGL_FN(glClientActiveTexture)
+#undef glClientWaitSync
+#define glClientWaitSync QEGL_FN(glClientWaitSync)
+#undef glClipPlane
+#define glClipPlane QEGL_FN(glClipPlane)
+#undef glColor3b
+#define glColor3b QEGL_FN(glColor3b)
+#undef glColor3bv
+#define glColor3bv QEGL_FN(glColor3bv)
+#undef glColor3d
+#define glColor3d QEGL_FN(glColor3d)
+#undef glColor3dv
+#define glColor3dv QEGL_FN(glColor3dv)
+#undef glColor3f
+#define glColor3f QEGL_FN(glColor3f)
+#undef glColor3fv
+#define glColor3fv QEGL_FN(glColor3fv)
+#undef glColor3i
+#define glColor3i QEGL_FN(glColor3i)
+#undef glColor3iv
+#define glColor3iv QEGL_FN(glColor3iv)
+#undef glColor3s
+#define glColor3s QEGL_FN(glColor3s)
+#undef glColor3sv
+#define glColor3sv QEGL_FN(glColor3sv)
+#undef glColor3ub
+#define glColor3ub QEGL_FN(glColor3ub)
+#undef glColor3ubv
+#define glColor3ubv QEGL_FN(glColor3ubv)
+#undef glColor3ui
+#define glColor3ui QEGL_FN(glColor3ui)
+#undef glColor3uiv
+#define glColor3uiv QEGL_FN(glColor3uiv)
+#undef glColor3us
+#define glColor3us QEGL_FN(glColor3us)
+#undef glColor3usv
+#define glColor3usv QEGL_FN(glColor3usv)
+#undef glColor4b
+#define glColor4b QEGL_FN(glColor4b)
+#undef glColor4bv
+#define glColor4bv QEGL_FN(glColor4bv)
+#undef glColor4d
+#define glColor4d QEGL_FN(glColor4d)
+#undef glColor4dv
+#define glColor4dv QEGL_FN(glColor4dv)
+#undef glColor4f
+#define glColor4f QEGL_FN(glColor4f)
+#undef glColor4fv
+#define glColor4fv QEGL_FN(glColor4fv)
+#undef glColor4i
+#define glColor4i QEGL_FN(glColor4i)
+#undef glColor4iv
+#define glColor4iv QEGL_FN(glColor4iv)
+#undef glColor4s
+#define glColor4s QEGL_FN(glColor4s)
+#undef glColor4sv
+#define glColor4sv QEGL_FN(glColor4sv)
+#undef glColor4ub
+#define glColor4ub QEGL_FN(glColor4ub)
+#undef glColor4ubv
+#define glColor4ubv QEGL_FN(glColor4ubv)
+#undef glColor4ui
+#define glColor4ui QEGL_FN(glColor4ui)
+#undef glColor4uiv
+#define glColor4uiv QEGL_FN(glColor4uiv)
+#undef glColor4us
+#define glColor4us QEGL_FN(glColor4us)
+#undef glColor4usv
+#define glColor4usv QEGL_FN(glColor4usv)
+#undef glColorMask
+#define glColorMask QEGL_FN(glColorMask)
+#undef glColorMaski
+#define glColorMaski QEGL_FN(glColorMaski)
+#undef glColorMaterial
+#define glColorMaterial QEGL_FN(glColorMaterial)
+#undef glColorPointer
+#define glColorPointer QEGL_FN(glColorPointer)
+#undef glColorTableParameterfv
+#define glColorTableParameterfv QEGL_FN(glColorTableParameterfv)
+#undef glColorTableParameteriv
+#define glColorTableParameteriv QEGL_FN(glColorTableParameteriv)
+#undef glCombinerInputNV
+#define glCombinerInputNV QEGL_FN(glCombinerInputNV)
+#undef glCombinerOutputNV
+#define glCombinerOutputNV QEGL_FN(glCombinerOutputNV)
+#undef glCombinerParameterfNV
+#define glCombinerParameterfNV QEGL_FN(glCombinerParameterfNV)
+#undef glCombinerParameterfvNV
+#define glCombinerParameterfvNV QEGL_FN(glCombinerParameterfvNV)
+#undef glCombinerParameteriNV
+#define glCombinerParameteriNV QEGL_FN(glCombinerParameteriNV)
+#undef glCombinerParameterivNV
+#define glCombinerParameterivNV QEGL_FN(glCombinerParameterivNV)
+#undef glCombinerStageParameterfvNV
+#define glCombinerStageParameterfvNV QEGL_FN(glCombinerStageParameterfvNV)
+#undef glCompileShaderARB
+#define glCompileShaderARB QEGL_FN(glCompileShaderARB)
+#undef glCompressedTexImage2D
+#define glCompressedTexImage2D QEGL_FN(glCompressedTexImage2D)
+#undef glCompressedTexImage3D
+#define glCompressedTexImage3D QEGL_FN(glCompressedTexImage3D)
+#undef glCompressedTexSubImage2D
+#define glCompressedTexSubImage2D QEGL_FN(glCompressedTexSubImage2D)
+#undef glCompressedTexSubImage3D
+#define glCompressedTexSubImage3D QEGL_FN(glCompressedTexSubImage3D)
+#undef glConvolutionParameterf
+#define glConvolutionParameterf QEGL_FN(glConvolutionParameterf)
+#undef glConvolutionParameterfv
+#define glConvolutionParameterfv QEGL_FN(glConvolutionParameterfv)
+#undef glConvolutionParameteri
+#define glConvolutionParameteri QEGL_FN(glConvolutionParameteri)
+#undef glConvolutionParameteriv
+#define glConvolutionParameteriv QEGL_FN(glConvolutionParameteriv)
+#undef glCopyBufferSubData
+#define glCopyBufferSubData QEGL_FN(glCopyBufferSubData)
+#undef glCopyColorSubTable
+#define glCopyColorSubTable QEGL_FN(glCopyColorSubTable)
+#undef glCopyColorTable
+#define glCopyColorTable QEGL_FN(glCopyColorTable)
+#undef glCopyConvolutionFilter1D
+#define glCopyConvolutionFilter1D QEGL_FN(glCopyConvolutionFilter1D)
+#undef glCopyConvolutionFilter2D
+#define glCopyConvolutionFilter2D QEGL_FN(glCopyConvolutionFilter2D)
+#undef glCopyImageSubData
+#define glCopyImageSubData QEGL_FN(glCopyImageSubData)
+#undef glCopyPixels
+#define glCopyPixels QEGL_FN(glCopyPixels)
+#undef glCopyTexImage1D
+#define glCopyTexImage1D QEGL_FN(glCopyTexImage1D)
+#undef glCopyTexImage2D
+#define glCopyTexImage2D QEGL_FN(glCopyTexImage2D)
+#undef glCopyTexSubImage1D
+#define glCopyTexSubImage1D QEGL_FN(glCopyTexSubImage1D)
+#undef glCopyTexSubImage2D
+#define glCopyTexSubImage2D QEGL_FN(glCopyTexSubImage2D)
+#undef glCopyTexSubImage3D
+#define glCopyTexSubImage3D QEGL_FN(glCopyTexSubImage3D)
+#undef glCreateMemoryObjectsEXT
+#define glCreateMemoryObjectsEXT QEGL_FN(glCreateMemoryObjectsEXT)
+#undef glCreateProgramObjectARB
+#define glCreateProgramObjectARB QEGL_FN(glCreateProgramObjectARB)
+#undef glCreateShaderObjectARB
+#define glCreateShaderObjectARB QEGL_FN(glCreateShaderObjectARB)
+#undef glCullFace
+#define glCullFace QEGL_FN(glCullFace)
+#undef glDebugMessageCallback
+#define glDebugMessageCallback QEGL_FN(glDebugMessageCallback)
+#undef glDeleteBuffers
+#define glDeleteBuffers QEGL_FN(glDeleteBuffers)
+#undef glDeleteFramebuffers
+#define glDeleteFramebuffers QEGL_FN(glDeleteFramebuffers)
+#undef glDeleteFramebuffersEXT
+#define glDeleteFramebuffersEXT QEGL_FN(glDeleteFramebuffersEXT)
+#undef glDeleteLists
+#define glDeleteLists QEGL_FN(glDeleteLists)
+#undef glDeleteMemoryObjectsEXT
+#define glDeleteMemoryObjectsEXT QEGL_FN(glDeleteMemoryObjectsEXT)
+#undef glDeleteObjectARB
+#define glDeleteObjectARB QEGL_FN(glDeleteObjectARB)
+#undef glDeleteProgramsARB
+#define glDeleteProgramsARB QEGL_FN(glDeleteProgramsARB)
+#undef glDeleteQueries
+#define glDeleteQueries QEGL_FN(glDeleteQueries)
+#undef glDeleteRenderbuffers
+#define glDeleteRenderbuffers QEGL_FN(glDeleteRenderbuffers)
+#undef glDeleteRenderbuffersEXT
+#define glDeleteRenderbuffersEXT QEGL_FN(glDeleteRenderbuffersEXT)
+#undef glDeleteSamplers
+#define glDeleteSamplers QEGL_FN(glDeleteSamplers)
+#undef glDeleteSemaphoresEXT
+#define glDeleteSemaphoresEXT QEGL_FN(glDeleteSemaphoresEXT)
+#undef glDeleteSync
+#define glDeleteSync QEGL_FN(glDeleteSync)
+#undef glDeleteTextures
+#define glDeleteTextures QEGL_FN(glDeleteTextures)
+#undef glDeleteTransformFeedbacks
+#define glDeleteTransformFeedbacks QEGL_FN(glDeleteTransformFeedbacks)
+#undef glDeleteVertexArrays
+#define glDeleteVertexArrays QEGL_FN(glDeleteVertexArrays)
+#undef glDepthBoundsEXT
+#define glDepthBoundsEXT QEGL_FN(glDepthBoundsEXT)
+#undef glDepthFunc
+#define glDepthFunc QEGL_FN(glDepthFunc)
+#undef glDepthMask
+#define glDepthMask QEGL_FN(glDepthMask)
+#undef glDepthRange
+#define glDepthRange QEGL_FN(glDepthRange)
+#undef glDetachObjectARB
+#define glDetachObjectARB QEGL_FN(glDetachObjectARB)
+#undef glDisable
+#define glDisable QEGL_FN(glDisable)
+#undef glDisableClientState
+#define glDisableClientState QEGL_FN(glDisableClientState)
+#undef glDisableVertexAttribArrayARB
+#define glDisableVertexAttribArrayARB QEGL_FN(glDisableVertexAttribArrayARB)
+#undef glDisablei
+#define glDisablei QEGL_FN(glDisablei)
+#undef glDrawArrays
+#define glDrawArrays QEGL_FN(glDrawArrays)
+#undef glDrawArraysInstanced
+#define glDrawArraysInstanced QEGL_FN(glDrawArraysInstanced)
+#undef glDrawBuffer
+#define glDrawBuffer QEGL_FN(glDrawBuffer)
+#undef glDrawBuffers
+#define glDrawBuffers QEGL_FN(glDrawBuffers)
+#undef glDrawElementsInstanced
+#define glDrawElementsInstanced QEGL_FN(glDrawElementsInstanced)
+#undef glDrawPixels
+#define glDrawPixels QEGL_FN(glDrawPixels)
+#undef glDrawRangeElements
+#define glDrawRangeElements QEGL_FN(glDrawRangeElements)
+#undef glEdgeFlag
+#define glEdgeFlag QEGL_FN(glEdgeFlag)
+#undef glEdgeFlagPointer
+#define glEdgeFlagPointer QEGL_FN(glEdgeFlagPointer)
+#undef glEdgeFlagv
+#define glEdgeFlagv QEGL_FN(glEdgeFlagv)
+#undef glEnable
+#define glEnable QEGL_FN(glEnable)
+#undef glEnableClientState
+#define glEnableClientState QEGL_FN(glEnableClientState)
+#undef glEnableVertexAttribArrayARB
+#define glEnableVertexAttribArrayARB QEGL_FN(glEnableVertexAttribArrayARB)
+#undef glEnablei
+#define glEnablei QEGL_FN(glEnablei)
+#undef glEnd
+#define glEnd QEGL_FN(glEnd)
+#undef glEndList
+#define glEndList QEGL_FN(glEndList)
+#undef glEndQuery
+#define glEndQuery QEGL_FN(glEndQuery)
+#undef glEndTransformFeedback
+#define glEndTransformFeedback QEGL_FN(glEndTransformFeedback)
+#undef glEvalCoord1d
+#define glEvalCoord1d QEGL_FN(glEvalCoord1d)
+#undef glEvalCoord1dv
+#define glEvalCoord1dv QEGL_FN(glEvalCoord1dv)
+#undef glEvalCoord1f
+#define glEvalCoord1f QEGL_FN(glEvalCoord1f)
+#undef glEvalCoord1fv
+#define glEvalCoord1fv QEGL_FN(glEvalCoord1fv)
+#undef glEvalCoord2d
+#define glEvalCoord2d QEGL_FN(glEvalCoord2d)
+#undef glEvalCoord2dv
+#define glEvalCoord2dv QEGL_FN(glEvalCoord2dv)
+#undef glEvalCoord2f
+#define glEvalCoord2f QEGL_FN(glEvalCoord2f)
+#undef glEvalCoord2fv
+#define glEvalCoord2fv QEGL_FN(glEvalCoord2fv)
+#undef glEvalMesh1
+#define glEvalMesh1 QEGL_FN(glEvalMesh1)
+#undef glEvalMesh2
+#define glEvalMesh2 QEGL_FN(glEvalMesh2)
+#undef glEvalPoint1
+#define glEvalPoint1 QEGL_FN(glEvalPoint1)
+#undef glEvalPoint2
+#define glEvalPoint2 QEGL_FN(glEvalPoint2)
+#undef glFenceSync
+#define glFenceSync QEGL_FN(glFenceSync)
+#undef glFinalCombinerInputNV
+#define glFinalCombinerInputNV QEGL_FN(glFinalCombinerInputNV)
+#undef glFinish
+#define glFinish QEGL_FN(glFinish)
+#undef glFlush
+#define glFlush QEGL_FN(glFlush)
+#undef glFogCoordPointer
+#define glFogCoordPointer QEGL_FN(glFogCoordPointer)
+#undef glFogCoordd
+#define glFogCoordd QEGL_FN(glFogCoordd)
+#undef glFogCoorddv
+#define glFogCoorddv QEGL_FN(glFogCoorddv)
+#undef glFogCoordf
+#define glFogCoordf QEGL_FN(glFogCoordf)
+#undef glFogCoordfv
+#define glFogCoordfv QEGL_FN(glFogCoordfv)
+#undef glFogf
+#define glFogf QEGL_FN(glFogf)
+#undef glFogfv
+#define glFogfv QEGL_FN(glFogfv)
+#undef glFogi
+#define glFogi QEGL_FN(glFogi)
+#undef glFogiv
+#define glFogiv QEGL_FN(glFogiv)
+#undef glFramebufferRenderbuffer
+#define glFramebufferRenderbuffer QEGL_FN(glFramebufferRenderbuffer)
+#undef glFramebufferRenderbufferEXT
+#define glFramebufferRenderbufferEXT QEGL_FN(glFramebufferRenderbufferEXT)
+#undef glFramebufferTexture1DEXT
+#define glFramebufferTexture1DEXT QEGL_FN(glFramebufferTexture1DEXT)
+#undef glFramebufferTexture2D
+#define glFramebufferTexture2D QEGL_FN(glFramebufferTexture2D)
+#undef glFramebufferTexture2DEXT
+#define glFramebufferTexture2DEXT QEGL_FN(glFramebufferTexture2DEXT)
+#undef glFramebufferTexture3DEXT
+#define glFramebufferTexture3DEXT QEGL_FN(glFramebufferTexture3DEXT)
+#undef glFramebufferTextureLayer
+#define glFramebufferTextureLayer QEGL_FN(glFramebufferTextureLayer)
+#undef glFrontFace
+#define glFrontFace QEGL_FN(glFrontFace)
+#undef glFrustum
+#define glFrustum QEGL_FN(glFrustum)
+#undef glGenFramebuffers
+#define glGenFramebuffers QEGL_FN(glGenFramebuffers)
+#undef glGenSamplers
+#define glGenSamplers QEGL_FN(glGenSamplers)
+#undef glGenSemaphoresEXT
+#define glGenSemaphoresEXT QEGL_FN(glGenSemaphoresEXT)
+#undef glGenTransformFeedbacks
+#define glGenTransformFeedbacks QEGL_FN(glGenTransformFeedbacks)
+#undef glGenVertexArrays
+#define glGenVertexArrays QEGL_FN(glGenVertexArrays)
+#undef glGenerateMipmapEXT
+#define glGenerateMipmapEXT QEGL_FN(glGenerateMipmapEXT)
+#undef glGetActiveAttribARB
+#define glGetActiveAttribARB QEGL_FN(glGetActiveAttribARB)
+#undef glGetActiveUniformARB
+#define glGetActiveUniformARB QEGL_FN(glGetActiveUniformARB)
+#undef glGetActiveUniformBlockName
+#define glGetActiveUniformBlockName QEGL_FN(glGetActiveUniformBlockName)
+#undef glGetActiveUniformBlockiv
+#define glGetActiveUniformBlockiv QEGL_FN(glGetActiveUniformBlockiv)
+#undef glGetActiveUniformsiv
+#define glGetActiveUniformsiv QEGL_FN(glGetActiveUniformsiv)
+#undef glGetAttachedObjectsARB
+#define glGetAttachedObjectsARB QEGL_FN(glGetAttachedObjectsARB)
+#undef glGetAttribLocationARB
+#define glGetAttribLocationARB QEGL_FN(glGetAttribLocationARB)
+#undef glGetBooleani_v
+#define glGetBooleani_v QEGL_FN(glGetBooleani_v)
+#undef glGetBooleanv
+#define glGetBooleanv QEGL_FN(glGetBooleanv)
+#undef glGetBufferParameteri64v
+#define glGetBufferParameteri64v QEGL_FN(glGetBufferParameteri64v)
+#undef glGetBufferParameteriv
+#define glGetBufferParameteriv QEGL_FN(glGetBufferParameteriv)
+#undef glGetBufferSubData
+#define glGetBufferSubData QEGL_FN(glGetBufferSubData)
+#undef glGetClipPlane
+#define glGetClipPlane QEGL_FN(glGetClipPlane)
+#undef glGetColorTableParameterfv
+#define glGetColorTableParameterfv QEGL_FN(glGetColorTableParameterfv)
+#undef glGetColorTableParameteriv
+#define glGetColorTableParameteriv QEGL_FN(glGetColorTableParameteriv)
+#undef glGetCombinerInputParameterfvNV
+#define glGetCombinerInputParameterfvNV QEGL_FN(glGetCombinerInputParameterfvNV)
+#undef glGetCombinerInputParameterivNV
+#define glGetCombinerInputParameterivNV QEGL_FN(glGetCombinerInputParameterivNV)
+#undef glGetCombinerOutputParameterfvNV
+#define glGetCombinerOutputParameterfvNV QEGL_FN(glGetCombinerOutputParameterfvNV)
+#undef glGetCombinerOutputParameterivNV
+#define glGetCombinerOutputParameterivNV QEGL_FN(glGetCombinerOutputParameterivNV)
+#undef glGetCombinerStageParameterfvNV
+#define glGetCombinerStageParameterfvNV QEGL_FN(glGetCombinerStageParameterfvNV)
+#undef glGetConvolutionParameterfv
+#define glGetConvolutionParameterfv QEGL_FN(glGetConvolutionParameterfv)
+#undef glGetConvolutionParameteriv
+#define glGetConvolutionParameteriv QEGL_FN(glGetConvolutionParameteriv)
+#undef glGetDoublev
+#define glGetDoublev QEGL_FN(glGetDoublev)
+#undef glGetError
+#define glGetError QEGL_FN(glGetError)
+#undef glGetFinalCombinerInputParameterfvNV
+#define glGetFinalCombinerInputParameterfvNV QEGL_FN(glGetFinalCombinerInputParameterfvNV)
+#undef glGetFinalCombinerInputParameterivNV
+#define glGetFinalCombinerInputParameterivNV QEGL_FN(glGetFinalCombinerInputParameterivNV)
+#undef glGetFloatv
+#define glGetFloatv QEGL_FN(glGetFloatv)
+#undef glGetFragDataLocation
+#define glGetFragDataLocation QEGL_FN(glGetFragDataLocation)
+#undef glGetFramebufferAttachmentParameteriv
+#define glGetFramebufferAttachmentParameteriv QEGL_FN(glGetFramebufferAttachmentParameteriv)
+#undef glGetFramebufferAttachmentParameterivEXT
+#define glGetFramebufferAttachmentParameterivEXT QEGL_FN(glGetFramebufferAttachmentParameterivEXT)
+#undef glGetHistogramParameterfv
+#define glGetHistogramParameterfv QEGL_FN(glGetHistogramParameterfv)
+#undef glGetHistogramParameteriv
+#define glGetHistogramParameteriv QEGL_FN(glGetHistogramParameteriv)
+#undef glGetInfoLogARB
+#define glGetInfoLogARB QEGL_FN(glGetInfoLogARB)
+#undef glGetInteger64i_v
+#define glGetInteger64i_v QEGL_FN(glGetInteger64i_v)
+#undef glGetInteger64v
+#define glGetInteger64v QEGL_FN(glGetInteger64v)
+#undef glGetIntegeri_v
+#define glGetIntegeri_v QEGL_FN(glGetIntegeri_v)
+#undef glGetIntegerv
+#define glGetIntegerv QEGL_FN(glGetIntegerv)
+#undef glGetInternalformativ
+#define glGetInternalformativ QEGL_FN(glGetInternalformativ)
+#undef glGetLightfv
+#define glGetLightfv QEGL_FN(glGetLightfv)
+#undef glGetLightiv
+#define glGetLightiv QEGL_FN(glGetLightiv)
+#undef glGetMaterialfv
+#define glGetMaterialfv QEGL_FN(glGetMaterialfv)
+#undef glGetMaterialiv
+#define glGetMaterialiv QEGL_FN(glGetMaterialiv)
+#undef glGetMinmaxParameterfv
+#define glGetMinmaxParameterfv QEGL_FN(glGetMinmaxParameterfv)
+#undef glGetMinmaxParameteriv
+#define glGetMinmaxParameteriv QEGL_FN(glGetMinmaxParameteriv)
+#undef glGetNamedFramebufferAttachmentParameteriv
+#define glGetNamedFramebufferAttachmentParameteriv QEGL_FN(glGetNamedFramebufferAttachmentParameteriv)
+#undef glGetObjectParameterfvARB
+#define glGetObjectParameterfvARB QEGL_FN(glGetObjectParameterfvARB)
+#undef glGetObjectParameterivARB
+#define glGetObjectParameterivARB QEGL_FN(glGetObjectParameterivARB)
+#undef glGetProgramEnvParameterdvARB
+#define glGetProgramEnvParameterdvARB QEGL_FN(glGetProgramEnvParameterdvARB)
+#undef glGetProgramEnvParameterfvARB
+#define glGetProgramEnvParameterfvARB QEGL_FN(glGetProgramEnvParameterfvARB)
+#undef glGetProgramInfoLog
+#define glGetProgramInfoLog QEGL_FN(glGetProgramInfoLog)
+#undef glGetProgramLocalParameterdvARB
+#define glGetProgramLocalParameterdvARB QEGL_FN(glGetProgramLocalParameterdvARB)
+#undef glGetProgramLocalParameterfvARB
+#define glGetProgramLocalParameterfvARB QEGL_FN(glGetProgramLocalParameterfvARB)
+#undef glGetProgramiv
+#define glGetProgramiv QEGL_FN(glGetProgramiv)
+#undef glGetProgramivARB
+#define glGetProgramivARB QEGL_FN(glGetProgramivARB)
+#undef glGetQueryObjecti64v
+#define glGetQueryObjecti64v QEGL_FN(glGetQueryObjecti64v)
+#undef glGetQueryObjectiv
+#define glGetQueryObjectiv QEGL_FN(glGetQueryObjectiv)
+#undef glGetQueryObjectui64v
+#define glGetQueryObjectui64v QEGL_FN(glGetQueryObjectui64v)
+#undef glGetQueryObjectuiv
+#define glGetQueryObjectuiv QEGL_FN(glGetQueryObjectuiv)
+#undef glGetQueryiv
+#define glGetQueryiv QEGL_FN(glGetQueryiv)
+#undef glGetRenderbufferParameterivEXT
+#define glGetRenderbufferParameterivEXT QEGL_FN(glGetRenderbufferParameterivEXT)
+#undef glGetSamplerParameterfv
+#define glGetSamplerParameterfv QEGL_FN(glGetSamplerParameterfv)
+#undef glGetSamplerParameteriv
+#define glGetSamplerParameteriv QEGL_FN(glGetSamplerParameteriv)
+#undef glGetShaderInfoLog
+#define glGetShaderInfoLog QEGL_FN(glGetShaderInfoLog)
+#undef glGetShaderSourceARB
+#define glGetShaderSourceARB QEGL_FN(glGetShaderSourceARB)
+#undef glGetShaderiv
+#define glGetShaderiv QEGL_FN(glGetShaderiv)
+#undef glGetString
+#define glGetString QEGL_FN(glGetString)
+#undef glGetStringi
+#define glGetStringi QEGL_FN(glGetStringi)
+#undef glGetSynciv
+#define glGetSynciv QEGL_FN(glGetSynciv)
+#undef glGetTexEnvfv
+#define glGetTexEnvfv QEGL_FN(glGetTexEnvfv)
+#undef glGetTexEnviv
+#define glGetTexEnviv QEGL_FN(glGetTexEnviv)
+#undef glGetTexGendv
+#define glGetTexGendv QEGL_FN(glGetTexGendv)
+#undef glGetTexGenfv
+#define glGetTexGenfv QEGL_FN(glGetTexGenfv)
+#undef glGetTexGeniv
+#define glGetTexGeniv QEGL_FN(glGetTexGeniv)
+#undef glGetTexLevelParameterfv
+#define glGetTexLevelParameterfv QEGL_FN(glGetTexLevelParameterfv)
+#undef glGetTexLevelParameteriv
+#define glGetTexLevelParameteriv QEGL_FN(glGetTexLevelParameteriv)
+#undef glGetTexParameterfv
+#define glGetTexParameterfv QEGL_FN(glGetTexParameterfv)
+#undef glGetTexParameteriv
+#define glGetTexParameteriv QEGL_FN(glGetTexParameteriv)
+#undef glGetTransformFeedbackVarying
+#define glGetTransformFeedbackVarying QEGL_FN(glGetTransformFeedbackVarying)
+#undef glGetUniformBlockIndex
+#define glGetUniformBlockIndex QEGL_FN(glGetUniformBlockIndex)
+#undef glGetUniformIndices
+#define glGetUniformIndices QEGL_FN(glGetUniformIndices)
+#undef glGetUniformLocationARB
+#define glGetUniformLocationARB QEGL_FN(glGetUniformLocationARB)
+#undef glGetUniformfvARB
+#define glGetUniformfvARB QEGL_FN(glGetUniformfvARB)
+#undef glGetUniformivARB
+#define glGetUniformivARB QEGL_FN(glGetUniformivARB)
+#undef glGetUniformuiv
+#define glGetUniformuiv QEGL_FN(glGetUniformuiv)
+#undef glGetUnsignedBytevEXT
+#define glGetUnsignedBytevEXT QEGL_FN(glGetUnsignedBytevEXT)
+#undef glGetVertexAttribIiv
+#define glGetVertexAttribIiv QEGL_FN(glGetVertexAttribIiv)
+#undef glGetVertexAttribIuiv
+#define glGetVertexAttribIuiv QEGL_FN(glGetVertexAttribIuiv)
+#undef glGetVertexAttribdvARB
+#define glGetVertexAttribdvARB QEGL_FN(glGetVertexAttribdvARB)
+#undef glGetVertexAttribfvARB
+#define glGetVertexAttribfvARB QEGL_FN(glGetVertexAttribfvARB)
+#undef glGetVertexAttribivARB
+#define glGetVertexAttribivARB QEGL_FN(glGetVertexAttribivARB)
+#undef glHint
+#define glHint QEGL_FN(glHint)
+#undef glHistogram
+#define glHistogram QEGL_FN(glHistogram)
+#undef glImportMemoryWin32HandleEXT
+#define glImportMemoryWin32HandleEXT QEGL_FN(glImportMemoryWin32HandleEXT)
+#undef glImportSemaphoreWin32HandleEXT
+#define glImportSemaphoreWin32HandleEXT QEGL_FN(glImportSemaphoreWin32HandleEXT)
+#undef glIndexMask
+#define glIndexMask QEGL_FN(glIndexMask)
+#undef glIndexPointer
+#define glIndexPointer QEGL_FN(glIndexPointer)
+#undef glIndexd
+#define glIndexd QEGL_FN(glIndexd)
+#undef glIndexdv
+#define glIndexdv QEGL_FN(glIndexdv)
+#undef glIndexf
+#define glIndexf QEGL_FN(glIndexf)
+#undef glIndexfv
+#define glIndexfv QEGL_FN(glIndexfv)
+#undef glIndexi
+#define glIndexi QEGL_FN(glIndexi)
+#undef glIndexiv
+#define glIndexiv QEGL_FN(glIndexiv)
+#undef glIndexs
+#define glIndexs QEGL_FN(glIndexs)
+#undef glIndexsv
+#define glIndexsv QEGL_FN(glIndexsv)
+#undef glIndexub
+#define glIndexub QEGL_FN(glIndexub)
+#undef glIndexubv
+#define glIndexubv QEGL_FN(glIndexubv)
+#undef glInitNames
+#define glInitNames QEGL_FN(glInitNames)
+#undef glInvalidateFramebuffer
+#define glInvalidateFramebuffer QEGL_FN(glInvalidateFramebuffer)
+#undef glInvalidateSubFramebuffer
+#define glInvalidateSubFramebuffer QEGL_FN(glInvalidateSubFramebuffer)
+#undef glIsBuffer
+#define glIsBuffer QEGL_FN(glIsBuffer)
+#undef glIsEnabled
+#define glIsEnabled QEGL_FN(glIsEnabled)
+#undef glIsEnabledi
+#define glIsEnabledi QEGL_FN(glIsEnabledi)
+#undef glIsFramebufferEXT
+#define glIsFramebufferEXT QEGL_FN(glIsFramebufferEXT)
+#undef glIsList
+#define glIsList QEGL_FN(glIsList)
+#undef glIsProgram
+#define glIsProgram QEGL_FN(glIsProgram)
+#undef glIsProgramARB
+#define glIsProgramARB QEGL_FN(glIsProgramARB)
+#undef glIsQuery
+#define glIsQuery QEGL_FN(glIsQuery)
+#undef glIsRenderbufferEXT
+#define glIsRenderbufferEXT QEGL_FN(glIsRenderbufferEXT)
+#undef glIsSampler
+#define glIsSampler QEGL_FN(glIsSampler)
+#undef glIsShader
+#define glIsShader QEGL_FN(glIsShader)
+#undef glIsSync
+#define glIsSync QEGL_FN(glIsSync)
+#undef glIsTexture
+#define glIsTexture QEGL_FN(glIsTexture)
+#undef glLightModelf
+#define glLightModelf QEGL_FN(glLightModelf)
+#undef glLightModelfv
+#define glLightModelfv QEGL_FN(glLightModelfv)
+#undef glLightModeli
+#define glLightModeli QEGL_FN(glLightModeli)
+#undef glLightModeliv
+#define glLightModeliv QEGL_FN(glLightModeliv)
+#undef glLightf
+#define glLightf QEGL_FN(glLightf)
+#undef glLightfv
+#define glLightfv QEGL_FN(glLightfv)
+#undef glLighti
+#define glLighti QEGL_FN(glLighti)
+#undef glLightiv
+#define glLightiv QEGL_FN(glLightiv)
+#undef glLineStipple
+#define glLineStipple QEGL_FN(glLineStipple)
+#undef glLineWidth
+#define glLineWidth QEGL_FN(glLineWidth)
+#undef glLinkProgramARB
+#define glLinkProgramARB QEGL_FN(glLinkProgramARB)
+#undef glListBase
+#define glListBase QEGL_FN(glListBase)
+#undef glLoadIdentity
+#define glLoadIdentity QEGL_FN(glLoadIdentity)
+#undef glLoadMatrixd
+#define glLoadMatrixd QEGL_FN(glLoadMatrixd)
+#undef glLoadMatrixf
+#define glLoadMatrixf QEGL_FN(glLoadMatrixf)
+#undef glLoadName
+#define glLoadName QEGL_FN(glLoadName)
+#undef glLoadTransposeMatrixd
+#define glLoadTransposeMatrixd QEGL_FN(glLoadTransposeMatrixd)
+#undef glLoadTransposeMatrixf
+#define glLoadTransposeMatrixf QEGL_FN(glLoadTransposeMatrixf)
+#undef glLogicOp
+#define glLogicOp QEGL_FN(glLogicOp)
+#undef glMapBufferRange
+#define glMapBufferRange QEGL_FN(glMapBufferRange)
+#undef glMapGrid1d
+#define glMapGrid1d QEGL_FN(glMapGrid1d)
+#undef glMapGrid1f
+#define glMapGrid1f QEGL_FN(glMapGrid1f)
+#undef glMapGrid2d
+#define glMapGrid2d QEGL_FN(glMapGrid2d)
+#undef glMapGrid2f
+#define glMapGrid2f QEGL_FN(glMapGrid2f)
+#undef glMaterialf
+#define glMaterialf QEGL_FN(glMaterialf)
+#undef glMaterialfv
+#define glMaterialfv QEGL_FN(glMaterialfv)
+#undef glMateriali
+#define glMateriali QEGL_FN(glMateriali)
+#undef glMaterialiv
+#define glMaterialiv QEGL_FN(glMaterialiv)
+#undef glMatrixMode
+#define glMatrixMode QEGL_FN(glMatrixMode)
+#undef glMemoryObjectParameterivEXT
+#define glMemoryObjectParameterivEXT QEGL_FN(glMemoryObjectParameterivEXT)
+#undef glMinmax
+#define glMinmax QEGL_FN(glMinmax)
+#undef glMultMatrixd
+#define glMultMatrixd QEGL_FN(glMultMatrixd)
+#undef glMultMatrixf
+#define glMultMatrixf QEGL_FN(glMultMatrixf)
+#undef glMultTransposeMatrixd
+#define glMultTransposeMatrixd QEGL_FN(glMultTransposeMatrixd)
+#undef glMultTransposeMatrixf
+#define glMultTransposeMatrixf QEGL_FN(glMultTransposeMatrixf)
+#undef glMultiTexCoord1d
+#define glMultiTexCoord1d QEGL_FN(glMultiTexCoord1d)
+#undef glMultiTexCoord1dv
+#define glMultiTexCoord1dv QEGL_FN(glMultiTexCoord1dv)
+#undef glMultiTexCoord1f
+#define glMultiTexCoord1f QEGL_FN(glMultiTexCoord1f)
+#undef glMultiTexCoord1fv
+#define glMultiTexCoord1fv QEGL_FN(glMultiTexCoord1fv)
+#undef glMultiTexCoord1i
+#define glMultiTexCoord1i QEGL_FN(glMultiTexCoord1i)
+#undef glMultiTexCoord1iv
+#define glMultiTexCoord1iv QEGL_FN(glMultiTexCoord1iv)
+#undef glMultiTexCoord1s
+#define glMultiTexCoord1s QEGL_FN(glMultiTexCoord1s)
+#undef glMultiTexCoord1sv
+#define glMultiTexCoord1sv QEGL_FN(glMultiTexCoord1sv)
+#undef glMultiTexCoord2d
+#define glMultiTexCoord2d QEGL_FN(glMultiTexCoord2d)
+#undef glMultiTexCoord2dv
+#define glMultiTexCoord2dv QEGL_FN(glMultiTexCoord2dv)
+#undef glMultiTexCoord2f
+#define glMultiTexCoord2f QEGL_FN(glMultiTexCoord2f)
+#undef glMultiTexCoord2fv
+#define glMultiTexCoord2fv QEGL_FN(glMultiTexCoord2fv)
+#undef glMultiTexCoord2i
+#define glMultiTexCoord2i QEGL_FN(glMultiTexCoord2i)
+#undef glMultiTexCoord2iv
+#define glMultiTexCoord2iv QEGL_FN(glMultiTexCoord2iv)
+#undef glMultiTexCoord2s
+#define glMultiTexCoord2s QEGL_FN(glMultiTexCoord2s)
+#undef glMultiTexCoord2sv
+#define glMultiTexCoord2sv QEGL_FN(glMultiTexCoord2sv)
+#undef glMultiTexCoord3d
+#define glMultiTexCoord3d QEGL_FN(glMultiTexCoord3d)
+#undef glMultiTexCoord3dv
+#define glMultiTexCoord3dv QEGL_FN(glMultiTexCoord3dv)
+#undef glMultiTexCoord3f
+#define glMultiTexCoord3f QEGL_FN(glMultiTexCoord3f)
+#undef glMultiTexCoord3fv
+#define glMultiTexCoord3fv QEGL_FN(glMultiTexCoord3fv)
+#undef glMultiTexCoord3i
+#define glMultiTexCoord3i QEGL_FN(glMultiTexCoord3i)
+#undef glMultiTexCoord3iv
+#define glMultiTexCoord3iv QEGL_FN(glMultiTexCoord3iv)
+#undef glMultiTexCoord3s
+#define glMultiTexCoord3s QEGL_FN(glMultiTexCoord3s)
+#undef glMultiTexCoord3sv
+#define glMultiTexCoord3sv QEGL_FN(glMultiTexCoord3sv)
+#undef glMultiTexCoord4d
+#define glMultiTexCoord4d QEGL_FN(glMultiTexCoord4d)
+#undef glMultiTexCoord4dv
+#define glMultiTexCoord4dv QEGL_FN(glMultiTexCoord4dv)
+#undef glMultiTexCoord4f
+#define glMultiTexCoord4f QEGL_FN(glMultiTexCoord4f)
+#undef glMultiTexCoord4fv
+#define glMultiTexCoord4fv QEGL_FN(glMultiTexCoord4fv)
+#undef glMultiTexCoord4i
+#define glMultiTexCoord4i QEGL_FN(glMultiTexCoord4i)
+#undef glMultiTexCoord4iv
+#define glMultiTexCoord4iv QEGL_FN(glMultiTexCoord4iv)
+#undef glMultiTexCoord4s
+#define glMultiTexCoord4s QEGL_FN(glMultiTexCoord4s)
+#undef glMultiTexCoord4sv
+#define glMultiTexCoord4sv QEGL_FN(glMultiTexCoord4sv)
+#undef glNewList
+#define glNewList QEGL_FN(glNewList)
+#undef glNormal3b
+#define glNormal3b QEGL_FN(glNormal3b)
+#undef glNormal3bv
+#define glNormal3bv QEGL_FN(glNormal3bv)
+#undef glNormal3d
+#define glNormal3d QEGL_FN(glNormal3d)
+#undef glNormal3dv
+#define glNormal3dv QEGL_FN(glNormal3dv)
+#undef glNormal3f
+#define glNormal3f QEGL_FN(glNormal3f)
+#undef glNormal3fv
+#define glNormal3fv QEGL_FN(glNormal3fv)
+#undef glNormal3i
+#define glNormal3i QEGL_FN(glNormal3i)
+#undef glNormal3iv
+#define glNormal3iv QEGL_FN(glNormal3iv)
+#undef glNormal3s
+#define glNormal3s QEGL_FN(glNormal3s)
+#undef glNormal3sv
+#define glNormal3sv QEGL_FN(glNormal3sv)
+#undef glNormalPointer
+#define glNormalPointer QEGL_FN(glNormalPointer)
+#undef glOrtho
+#define glOrtho QEGL_FN(glOrtho)
+#undef glPassThrough
+#define glPassThrough QEGL_FN(glPassThrough)
+#undef glPauseTransformFeedback
+#define glPauseTransformFeedback QEGL_FN(glPauseTransformFeedback)
+#undef glPixelStorei
+#define glPixelStorei QEGL_FN(glPixelStorei)
+#undef glPixelTransferf
+#define glPixelTransferf QEGL_FN(glPixelTransferf)
+#undef glPixelTransferi
+#define glPixelTransferi QEGL_FN(glPixelTransferi)
+#undef glPixelZoom
+#define glPixelZoom QEGL_FN(glPixelZoom)
+#undef glPointParameterf
+#define glPointParameterf QEGL_FN(glPointParameterf)
+#undef glPointParameterfv
+#define glPointParameterfv QEGL_FN(glPointParameterfv)
+#undef glPointParameteri
+#define glPointParameteri QEGL_FN(glPointParameteri)
+#undef glPointParameteriv
+#define glPointParameteriv QEGL_FN(glPointParameteriv)
+#undef glPointSize
+#define glPointSize QEGL_FN(glPointSize)
+#undef glPolygonMode
+#define glPolygonMode QEGL_FN(glPolygonMode)
+#undef glPolygonOffset
+#define glPolygonOffset QEGL_FN(glPolygonOffset)
+#undef glPolygonStipple
+#define glPolygonStipple QEGL_FN(glPolygonStipple)
+#undef glPopAttrib
+#define glPopAttrib QEGL_FN(glPopAttrib)
+#undef glPopMatrix
+#define glPopMatrix QEGL_FN(glPopMatrix)
+#undef glPopName
+#define glPopName QEGL_FN(glPopName)
+#undef glPrimitiveRestartIndex
+#define glPrimitiveRestartIndex QEGL_FN(glPrimitiveRestartIndex)
+#undef glPrioritizeTextures
+#define glPrioritizeTextures QEGL_FN(glPrioritizeTextures)
+#undef glProgramEnvParameter4dARB
+#define glProgramEnvParameter4dARB QEGL_FN(glProgramEnvParameter4dARB)
+#undef glProgramEnvParameter4dvARB
+#define glProgramEnvParameter4dvARB QEGL_FN(glProgramEnvParameter4dvARB)
+#undef glProgramEnvParameter4fARB
+#define glProgramEnvParameter4fARB QEGL_FN(glProgramEnvParameter4fARB)
+#undef glProgramEnvParameter4fvARB
+#define glProgramEnvParameter4fvARB QEGL_FN(glProgramEnvParameter4fvARB)
+#undef glProgramEnvParameters4fvEXT
+#define glProgramEnvParameters4fvEXT QEGL_FN(glProgramEnvParameters4fvEXT)
+#undef glProgramLocalParameter4dARB
+#define glProgramLocalParameter4dARB QEGL_FN(glProgramLocalParameter4dARB)
+#undef glProgramLocalParameter4dvARB
+#define glProgramLocalParameter4dvARB QEGL_FN(glProgramLocalParameter4dvARB)
+#undef glProgramLocalParameter4fARB
+#define glProgramLocalParameter4fARB QEGL_FN(glProgramLocalParameter4fARB)
+#undef glProgramLocalParameter4fvARB
+#define glProgramLocalParameter4fvARB QEGL_FN(glProgramLocalParameter4fvARB)
+#undef glProgramLocalParameters4fvEXT
+#define glProgramLocalParameters4fvEXT QEGL_FN(glProgramLocalParameters4fvEXT)
+#undef glProgramStringARB
+#define glProgramStringARB QEGL_FN(glProgramStringARB)
+#undef glPushAttrib
+#define glPushAttrib QEGL_FN(glPushAttrib)
+#undef glPushMatrix
+#define glPushMatrix QEGL_FN(glPushMatrix)
+#undef glPushName
+#define glPushName QEGL_FN(glPushName)
+#undef glQueryCounter
+#define glQueryCounter QEGL_FN(glQueryCounter)
+#undef glRasterPos2d
+#define glRasterPos2d QEGL_FN(glRasterPos2d)
+#undef glRasterPos2dv
+#define glRasterPos2dv QEGL_FN(glRasterPos2dv)
+#undef glRasterPos2f
+#define glRasterPos2f QEGL_FN(glRasterPos2f)
+#undef glRasterPos2fv
+#define glRasterPos2fv QEGL_FN(glRasterPos2fv)
+#undef glRasterPos2i
+#define glRasterPos2i QEGL_FN(glRasterPos2i)
+#undef glRasterPos2iv
+#define glRasterPos2iv QEGL_FN(glRasterPos2iv)
+#undef glRasterPos2s
+#define glRasterPos2s QEGL_FN(glRasterPos2s)
+#undef glRasterPos2sv
+#define glRasterPos2sv QEGL_FN(glRasterPos2sv)
+#undef glRasterPos3d
+#define glRasterPos3d QEGL_FN(glRasterPos3d)
+#undef glRasterPos3dv
+#define glRasterPos3dv QEGL_FN(glRasterPos3dv)
+#undef glRasterPos3f
+#define glRasterPos3f QEGL_FN(glRasterPos3f)
+#undef glRasterPos3fv
+#define glRasterPos3fv QEGL_FN(glRasterPos3fv)
+#undef glRasterPos3i
+#define glRasterPos3i QEGL_FN(glRasterPos3i)
+#undef glRasterPos3iv
+#define glRasterPos3iv QEGL_FN(glRasterPos3iv)
+#undef glRasterPos3s
+#define glRasterPos3s QEGL_FN(glRasterPos3s)
+#undef glRasterPos3sv
+#define glRasterPos3sv QEGL_FN(glRasterPos3sv)
+#undef glRasterPos4d
+#define glRasterPos4d QEGL_FN(glRasterPos4d)
+#undef glRasterPos4dv
+#define glRasterPos4dv QEGL_FN(glRasterPos4dv)
+#undef glRasterPos4f
+#define glRasterPos4f QEGL_FN(glRasterPos4f)
+#undef glRasterPos4fv
+#define glRasterPos4fv QEGL_FN(glRasterPos4fv)
+#undef glRasterPos4i
+#define glRasterPos4i QEGL_FN(glRasterPos4i)
+#undef glRasterPos4iv
+#define glRasterPos4iv QEGL_FN(glRasterPos4iv)
+#undef glRasterPos4s
+#define glRasterPos4s QEGL_FN(glRasterPos4s)
+#undef glRasterPos4sv
+#define glRasterPos4sv QEGL_FN(glRasterPos4sv)
+#undef glReadBuffer
+#define glReadBuffer QEGL_FN(glReadBuffer)
+#undef glReadPixels
+#define glReadPixels QEGL_FN(glReadPixels)
+#undef glRectd
+#define glRectd QEGL_FN(glRectd)
+#undef glRectdv
+#define glRectdv QEGL_FN(glRectdv)
+#undef glRectf
+#define glRectf QEGL_FN(glRectf)
+#undef glRectfv
+#define glRectfv QEGL_FN(glRectfv)
+#undef glRecti
+#define glRecti QEGL_FN(glRecti)
+#undef glRectiv
+#define glRectiv QEGL_FN(glRectiv)
+#undef glRects
+#define glRects QEGL_FN(glRects)
+#undef glRectsv
+#define glRectsv QEGL_FN(glRectsv)
+#undef glRenderMode
+#define glRenderMode QEGL_FN(glRenderMode)
+#undef glRenderbufferStorage
+#define glRenderbufferStorage QEGL_FN(glRenderbufferStorage)
+#undef glRenderbufferStorageEXT
+#define glRenderbufferStorageEXT QEGL_FN(glRenderbufferStorageEXT)
+#undef glRenderbufferStorageMultisample
+#define glRenderbufferStorageMultisample QEGL_FN(glRenderbufferStorageMultisample)
+#undef glResetHistogram
+#define glResetHistogram QEGL_FN(glResetHistogram)
+#undef glResetMinmax
+#define glResetMinmax QEGL_FN(glResetMinmax)
+#undef glResumeTransformFeedback
+#define glResumeTransformFeedback QEGL_FN(glResumeTransformFeedback)
+#undef glRotated
+#define glRotated QEGL_FN(glRotated)
+#undef glRotatef
+#define glRotatef QEGL_FN(glRotatef)
+#undef glSampleCoverage
+#define glSampleCoverage QEGL_FN(glSampleCoverage)
+#undef glSamplerParameterf
+#define glSamplerParameterf QEGL_FN(glSamplerParameterf)
+#undef glSamplerParameterfv
+#define glSamplerParameterfv QEGL_FN(glSamplerParameterfv)
+#undef glSamplerParameteri
+#define glSamplerParameteri QEGL_FN(glSamplerParameteri)
+#undef glSamplerParameteriv
+#define glSamplerParameteriv QEGL_FN(glSamplerParameteriv)
+#undef glScaled
+#define glScaled QEGL_FN(glScaled)
+#undef glScalef
+#define glScalef QEGL_FN(glScalef)
+#undef glScissor
+#define glScissor QEGL_FN(glScissor)
+#undef glSecondaryColor3b
+#define glSecondaryColor3b QEGL_FN(glSecondaryColor3b)
+#undef glSecondaryColor3bv
+#define glSecondaryColor3bv QEGL_FN(glSecondaryColor3bv)
+#undef glSecondaryColor3d
+#define glSecondaryColor3d QEGL_FN(glSecondaryColor3d)
+#undef glSecondaryColor3dv
+#define glSecondaryColor3dv QEGL_FN(glSecondaryColor3dv)
+#undef glSecondaryColor3f
+#define glSecondaryColor3f QEGL_FN(glSecondaryColor3f)
+#undef glSecondaryColor3fv
+#define glSecondaryColor3fv QEGL_FN(glSecondaryColor3fv)
+#undef glSecondaryColor3i
+#define glSecondaryColor3i QEGL_FN(glSecondaryColor3i)
+#undef glSecondaryColor3iv
+#define glSecondaryColor3iv QEGL_FN(glSecondaryColor3iv)
+#undef glSecondaryColor3s
+#define glSecondaryColor3s QEGL_FN(glSecondaryColor3s)
+#undef glSecondaryColor3sv
+#define glSecondaryColor3sv QEGL_FN(glSecondaryColor3sv)
+#undef glSecondaryColor3ub
+#define glSecondaryColor3ub QEGL_FN(glSecondaryColor3ub)
+#undef glSecondaryColor3ubv
+#define glSecondaryColor3ubv QEGL_FN(glSecondaryColor3ubv)
+#undef glSecondaryColor3ui
+#define glSecondaryColor3ui QEGL_FN(glSecondaryColor3ui)
+#undef glSecondaryColor3uiv
+#define glSecondaryColor3uiv QEGL_FN(glSecondaryColor3uiv)
+#undef glSecondaryColor3us
+#define glSecondaryColor3us QEGL_FN(glSecondaryColor3us)
+#undef glSecondaryColor3usv
+#define glSecondaryColor3usv QEGL_FN(glSecondaryColor3usv)
+#undef glSecondaryColorPointer
+#define glSecondaryColorPointer QEGL_FN(glSecondaryColorPointer)
+#undef glSemaphoreParameterui64vEXT
+#define glSemaphoreParameterui64vEXT QEGL_FN(glSemaphoreParameterui64vEXT)
+#undef glShadeModel
+#define glShadeModel QEGL_FN(glShadeModel)
+#undef glShaderSourceARB
+#define glShaderSourceARB QEGL_FN(glShaderSourceARB)
+#undef glSignalSemaphoreEXT
+#define glSignalSemaphoreEXT QEGL_FN(glSignalSemaphoreEXT)
+#undef glStencilFunc
+#define glStencilFunc QEGL_FN(glStencilFunc)
+#undef glStencilFuncSeparate
+#define glStencilFuncSeparate QEGL_FN(glStencilFuncSeparate)
+#undef glStencilMask
+#define glStencilMask QEGL_FN(glStencilMask)
+#undef glStencilMaskSeparate
+#define glStencilMaskSeparate QEGL_FN(glStencilMaskSeparate)
+#undef glStencilOp
+#define glStencilOp QEGL_FN(glStencilOp)
+#undef glStencilOpSeparate
+#define glStencilOpSeparate QEGL_FN(glStencilOpSeparate)
+#undef glTexCoord1d
+#define glTexCoord1d QEGL_FN(glTexCoord1d)
+#undef glTexCoord1dv
+#define glTexCoord1dv QEGL_FN(glTexCoord1dv)
+#undef glTexCoord1f
+#define glTexCoord1f QEGL_FN(glTexCoord1f)
+#undef glTexCoord1fv
+#define glTexCoord1fv QEGL_FN(glTexCoord1fv)
+#undef glTexCoord1i
+#define glTexCoord1i QEGL_FN(glTexCoord1i)
+#undef glTexCoord1iv
+#define glTexCoord1iv QEGL_FN(glTexCoord1iv)
+#undef glTexCoord1s
+#define glTexCoord1s QEGL_FN(glTexCoord1s)
+#undef glTexCoord1sv
+#define glTexCoord1sv QEGL_FN(glTexCoord1sv)
+#undef glTexCoord2d
+#define glTexCoord2d QEGL_FN(glTexCoord2d)
+#undef glTexCoord2dv
+#define glTexCoord2dv QEGL_FN(glTexCoord2dv)
+#undef glTexCoord2f
+#define glTexCoord2f QEGL_FN(glTexCoord2f)
+#undef glTexCoord2fv
+#define glTexCoord2fv QEGL_FN(glTexCoord2fv)
+#undef glTexCoord2i
+#define glTexCoord2i QEGL_FN(glTexCoord2i)
+#undef glTexCoord2iv
+#define glTexCoord2iv QEGL_FN(glTexCoord2iv)
+#undef glTexCoord2s
+#define glTexCoord2s QEGL_FN(glTexCoord2s)
+#undef glTexCoord2sv
+#define glTexCoord2sv QEGL_FN(glTexCoord2sv)
+#undef glTexCoord3d
+#define glTexCoord3d QEGL_FN(glTexCoord3d)
+#undef glTexCoord3dv
+#define glTexCoord3dv QEGL_FN(glTexCoord3dv)
+#undef glTexCoord3f
+#define glTexCoord3f QEGL_FN(glTexCoord3f)
+#undef glTexCoord3fv
+#define glTexCoord3fv QEGL_FN(glTexCoord3fv)
+#undef glTexCoord3i
+#define glTexCoord3i QEGL_FN(glTexCoord3i)
+#undef glTexCoord3iv
+#define glTexCoord3iv QEGL_FN(glTexCoord3iv)
+#undef glTexCoord3s
+#define glTexCoord3s QEGL_FN(glTexCoord3s)
+#undef glTexCoord3sv
+#define glTexCoord3sv QEGL_FN(glTexCoord3sv)
+#undef glTexCoord4d
+#define glTexCoord4d QEGL_FN(glTexCoord4d)
+#undef glTexCoord4dv
+#define glTexCoord4dv QEGL_FN(glTexCoord4dv)
+#undef glTexCoord4f
+#define glTexCoord4f QEGL_FN(glTexCoord4f)
+#undef glTexCoord4fv
+#define glTexCoord4fv QEGL_FN(glTexCoord4fv)
+#undef glTexCoord4i
+#define glTexCoord4i QEGL_FN(glTexCoord4i)
+#undef glTexCoord4iv
+#define glTexCoord4iv QEGL_FN(glTexCoord4iv)
+#undef glTexCoord4s
+#define glTexCoord4s QEGL_FN(glTexCoord4s)
+#undef glTexCoord4sv
+#define glTexCoord4sv QEGL_FN(glTexCoord4sv)
+#undef glTexCoordPointer
+#define glTexCoordPointer QEGL_FN(glTexCoordPointer)
+#undef glTexEnvf
+#define glTexEnvf QEGL_FN(glTexEnvf)
+#undef glTexEnvfv
+#define glTexEnvfv QEGL_FN(glTexEnvfv)
+#undef glTexEnvi
+#define glTexEnvi QEGL_FN(glTexEnvi)
+#undef glTexEnviv
+#define glTexEnviv QEGL_FN(glTexEnviv)
+#undef glTexGend
+#define glTexGend QEGL_FN(glTexGend)
+#undef glTexGendv
+#define glTexGendv QEGL_FN(glTexGendv)
+#undef glTexGenf
+#define glTexGenf QEGL_FN(glTexGenf)
+#undef glTexGenfv
+#define glTexGenfv QEGL_FN(glTexGenfv)
+#undef glTexGeni
+#define glTexGeni QEGL_FN(glTexGeni)
+#undef glTexGeniv
+#define glTexGeniv QEGL_FN(glTexGeniv)
+#undef glTexImage1D
+#define glTexImage1D QEGL_FN(glTexImage1D)
+#undef glTexImage2D
+#define glTexImage2D QEGL_FN(glTexImage2D)
+#undef glTexImage3D
+#define glTexImage3D QEGL_FN(glTexImage3D)
+#undef glTexParameterf
+#define glTexParameterf QEGL_FN(glTexParameterf)
+#undef glTexParameterfv
+#define glTexParameterfv QEGL_FN(glTexParameterfv)
+#undef glTexParameteri
+#define glTexParameteri QEGL_FN(glTexParameteri)
+#undef glTexParameteriv
+#define glTexParameteriv QEGL_FN(glTexParameteriv)
+#undef glTexStorage2D
+#define glTexStorage2D QEGL_FN(glTexStorage2D)
+#undef glTexStorage3D
+#define glTexStorage3D QEGL_FN(glTexStorage3D)
+#undef glTexStorageMem2DEXT
+#define glTexStorageMem2DEXT QEGL_FN(glTexStorageMem2DEXT)
+#undef glTexSubImage1D
+#define glTexSubImage1D QEGL_FN(glTexSubImage1D)
+#undef glTexSubImage2D
+#define glTexSubImage2D QEGL_FN(glTexSubImage2D)
+#undef glTexSubImage3D
+#define glTexSubImage3D QEGL_FN(glTexSubImage3D)
+#undef glTransformFeedbackVaryings
+#define glTransformFeedbackVaryings QEGL_FN(glTransformFeedbackVaryings)
+#undef glTranslated
+#define glTranslated QEGL_FN(glTranslated)
+#undef glTranslatef
+#define glTranslatef QEGL_FN(glTranslatef)
+#undef glUniform1fARB
+#define glUniform1fARB QEGL_FN(glUniform1fARB)
+#undef glUniform1fvARB
+#define glUniform1fvARB QEGL_FN(glUniform1fvARB)
+#undef glUniform1iARB
+#define glUniform1iARB QEGL_FN(glUniform1iARB)
+#undef glUniform1ivARB
+#define glUniform1ivARB QEGL_FN(glUniform1ivARB)
+#undef glUniform1ui
+#define glUniform1ui QEGL_FN(glUniform1ui)
+#undef glUniform1uiv
+#define glUniform1uiv QEGL_FN(glUniform1uiv)
+#undef glUniform2fARB
+#define glUniform2fARB QEGL_FN(glUniform2fARB)
+#undef glUniform2fvARB
+#define glUniform2fvARB QEGL_FN(glUniform2fvARB)
+#undef glUniform2iARB
+#define glUniform2iARB QEGL_FN(glUniform2iARB)
+#undef glUniform2ivARB
+#define glUniform2ivARB QEGL_FN(glUniform2ivARB)
+#undef glUniform2ui
+#define glUniform2ui QEGL_FN(glUniform2ui)
+#undef glUniform2uiv
+#define glUniform2uiv QEGL_FN(glUniform2uiv)
+#undef glUniform3fARB
+#define glUniform3fARB QEGL_FN(glUniform3fARB)
+#undef glUniform3fvARB
+#define glUniform3fvARB QEGL_FN(glUniform3fvARB)
+#undef glUniform3iARB
+#define glUniform3iARB QEGL_FN(glUniform3iARB)
+#undef glUniform3ivARB
+#define glUniform3ivARB QEGL_FN(glUniform3ivARB)
+#undef glUniform3ui
+#define glUniform3ui QEGL_FN(glUniform3ui)
+#undef glUniform3uiv
+#define glUniform3uiv QEGL_FN(glUniform3uiv)
+#undef glUniform4fARB
+#define glUniform4fARB QEGL_FN(glUniform4fARB)
+#undef glUniform4fvARB
+#define glUniform4fvARB QEGL_FN(glUniform4fvARB)
+#undef glUniform4iARB
+#define glUniform4iARB QEGL_FN(glUniform4iARB)
+#undef glUniform4ivARB
+#define glUniform4ivARB QEGL_FN(glUniform4ivARB)
+#undef glUniform4ui
+#define glUniform4ui QEGL_FN(glUniform4ui)
+#undef glUniform4uiv
+#define glUniform4uiv QEGL_FN(glUniform4uiv)
+#undef glUniformBlockBinding
+#define glUniformBlockBinding QEGL_FN(glUniformBlockBinding)
+#undef glUniformMatrix2fvARB
+#define glUniformMatrix2fvARB QEGL_FN(glUniformMatrix2fvARB)
+#undef glUniformMatrix2x3fv
+#define glUniformMatrix2x3fv QEGL_FN(glUniformMatrix2x3fv)
+#undef glUniformMatrix2x4fv
+#define glUniformMatrix2x4fv QEGL_FN(glUniformMatrix2x4fv)
+#undef glUniformMatrix3fvARB
+#define glUniformMatrix3fvARB QEGL_FN(glUniformMatrix3fvARB)
+#undef glUniformMatrix3x2fv
+#define glUniformMatrix3x2fv QEGL_FN(glUniformMatrix3x2fv)
+#undef glUniformMatrix3x4fv
+#define glUniformMatrix3x4fv QEGL_FN(glUniformMatrix3x4fv)
+#undef glUniformMatrix4fvARB
+#define glUniformMatrix4fvARB QEGL_FN(glUniformMatrix4fvARB)
+#undef glUniformMatrix4x2fv
+#define glUniformMatrix4x2fv QEGL_FN(glUniformMatrix4x2fv)
+#undef glUniformMatrix4x3fv
+#define glUniformMatrix4x3fv QEGL_FN(glUniformMatrix4x3fv)
+#undef glUnmapBuffer
+#define glUnmapBuffer QEGL_FN(glUnmapBuffer)
+#undef glUseProgramObjectARB
+#define glUseProgramObjectARB QEGL_FN(glUseProgramObjectARB)
+#undef glValidateProgramARB
+#define glValidateProgramARB QEGL_FN(glValidateProgramARB)
+#undef glVertex2d
+#define glVertex2d QEGL_FN(glVertex2d)
+#undef glVertex2dv
+#define glVertex2dv QEGL_FN(glVertex2dv)
+#undef glVertex2f
+#define glVertex2f QEGL_FN(glVertex2f)
+#undef glVertex2fv
+#define glVertex2fv QEGL_FN(glVertex2fv)
+#undef glVertex2i
+#define glVertex2i QEGL_FN(glVertex2i)
+#undef glVertex2iv
+#define glVertex2iv QEGL_FN(glVertex2iv)
+#undef glVertex2s
+#define glVertex2s QEGL_FN(glVertex2s)
+#undef glVertex2sv
+#define glVertex2sv QEGL_FN(glVertex2sv)
+#undef glVertex3d
+#define glVertex3d QEGL_FN(glVertex3d)
+#undef glVertex3dv
+#define glVertex3dv QEGL_FN(glVertex3dv)
+#undef glVertex3f
+#define glVertex3f QEGL_FN(glVertex3f)
+#undef glVertex3fv
+#define glVertex3fv QEGL_FN(glVertex3fv)
+#undef glVertex3i
+#define glVertex3i QEGL_FN(glVertex3i)
+#undef glVertex3iv
+#define glVertex3iv QEGL_FN(glVertex3iv)
+#undef glVertex3s
+#define glVertex3s QEGL_FN(glVertex3s)
+#undef glVertex3sv
+#define glVertex3sv QEGL_FN(glVertex3sv)
+#undef glVertex4d
+#define glVertex4d QEGL_FN(glVertex4d)
+#undef glVertex4dv
+#define glVertex4dv QEGL_FN(glVertex4dv)
+#undef glVertex4f
+#define glVertex4f QEGL_FN(glVertex4f)
+#undef glVertex4fv
+#define glVertex4fv QEGL_FN(glVertex4fv)
+#undef glVertex4i
+#define glVertex4i QEGL_FN(glVertex4i)
+#undef glVertex4iv
+#define glVertex4iv QEGL_FN(glVertex4iv)
+#undef glVertex4s
+#define glVertex4s QEGL_FN(glVertex4s)
+#undef glVertex4sv
+#define glVertex4sv QEGL_FN(glVertex4sv)
+#undef glVertexAttrib1dARB
+#define glVertexAttrib1dARB QEGL_FN(glVertexAttrib1dARB)
+#undef glVertexAttrib1dvARB
+#define glVertexAttrib1dvARB QEGL_FN(glVertexAttrib1dvARB)
+#undef glVertexAttrib1fARB
+#define glVertexAttrib1fARB QEGL_FN(glVertexAttrib1fARB)
+#undef glVertexAttrib1fvARB
+#define glVertexAttrib1fvARB QEGL_FN(glVertexAttrib1fvARB)
+#undef glVertexAttrib1sARB
+#define glVertexAttrib1sARB QEGL_FN(glVertexAttrib1sARB)
+#undef glVertexAttrib1svARB
+#define glVertexAttrib1svARB QEGL_FN(glVertexAttrib1svARB)
+#undef glVertexAttrib2dARB
+#define glVertexAttrib2dARB QEGL_FN(glVertexAttrib2dARB)
+#undef glVertexAttrib2dvARB
+#define glVertexAttrib2dvARB QEGL_FN(glVertexAttrib2dvARB)
+#undef glVertexAttrib2fARB
+#define glVertexAttrib2fARB QEGL_FN(glVertexAttrib2fARB)
+#undef glVertexAttrib2fvARB
+#define glVertexAttrib2fvARB QEGL_FN(glVertexAttrib2fvARB)
+#undef glVertexAttrib2sARB
+#define glVertexAttrib2sARB QEGL_FN(glVertexAttrib2sARB)
+#undef glVertexAttrib2svARB
+#define glVertexAttrib2svARB QEGL_FN(glVertexAttrib2svARB)
+#undef glVertexAttrib3dARB
+#define glVertexAttrib3dARB QEGL_FN(glVertexAttrib3dARB)
+#undef glVertexAttrib3dvARB
+#define glVertexAttrib3dvARB QEGL_FN(glVertexAttrib3dvARB)
+#undef glVertexAttrib3fARB
+#define glVertexAttrib3fARB QEGL_FN(glVertexAttrib3fARB)
+#undef glVertexAttrib3fvARB
+#define glVertexAttrib3fvARB QEGL_FN(glVertexAttrib3fvARB)
+#undef glVertexAttrib3sARB
+#define glVertexAttrib3sARB QEGL_FN(glVertexAttrib3sARB)
+#undef glVertexAttrib3svARB
+#define glVertexAttrib3svARB QEGL_FN(glVertexAttrib3svARB)
+#undef glVertexAttrib4NbvARB
+#define glVertexAttrib4NbvARB QEGL_FN(glVertexAttrib4NbvARB)
+#undef glVertexAttrib4NivARB
+#define glVertexAttrib4NivARB QEGL_FN(glVertexAttrib4NivARB)
+#undef glVertexAttrib4NsvARB
+#define glVertexAttrib4NsvARB QEGL_FN(glVertexAttrib4NsvARB)
+#undef glVertexAttrib4NubARB
+#define glVertexAttrib4NubARB QEGL_FN(glVertexAttrib4NubARB)
+#undef glVertexAttrib4NubvARB
+#define glVertexAttrib4NubvARB QEGL_FN(glVertexAttrib4NubvARB)
+#undef glVertexAttrib4NuivARB
+#define glVertexAttrib4NuivARB QEGL_FN(glVertexAttrib4NuivARB)
+#undef glVertexAttrib4NusvARB
+#define glVertexAttrib4NusvARB QEGL_FN(glVertexAttrib4NusvARB)
+#undef glVertexAttrib4bvARB
+#define glVertexAttrib4bvARB QEGL_FN(glVertexAttrib4bvARB)
+#undef glVertexAttrib4dARB
+#define glVertexAttrib4dARB QEGL_FN(glVertexAttrib4dARB)
+#undef glVertexAttrib4dvARB
+#define glVertexAttrib4dvARB QEGL_FN(glVertexAttrib4dvARB)
+#undef glVertexAttrib4fARB
+#define glVertexAttrib4fARB QEGL_FN(glVertexAttrib4fARB)
+#undef glVertexAttrib4fvARB
+#define glVertexAttrib4fvARB QEGL_FN(glVertexAttrib4fvARB)
+#undef glVertexAttrib4ivARB
+#define glVertexAttrib4ivARB QEGL_FN(glVertexAttrib4ivARB)
+#undef glVertexAttrib4sARB
+#define glVertexAttrib4sARB QEGL_FN(glVertexAttrib4sARB)
+#undef glVertexAttrib4svARB
+#define glVertexAttrib4svARB QEGL_FN(glVertexAttrib4svARB)
+#undef glVertexAttrib4ubvARB
+#define glVertexAttrib4ubvARB QEGL_FN(glVertexAttrib4ubvARB)
+#undef glVertexAttrib4uivARB
+#define glVertexAttrib4uivARB QEGL_FN(glVertexAttrib4uivARB)
+#undef glVertexAttrib4usvARB
+#define glVertexAttrib4usvARB QEGL_FN(glVertexAttrib4usvARB)
+#undef glVertexAttribDivisor
+#define glVertexAttribDivisor QEGL_FN(glVertexAttribDivisor)
+#undef glVertexAttribI4i
+#define glVertexAttribI4i QEGL_FN(glVertexAttribI4i)
+#undef glVertexAttribI4iv
+#define glVertexAttribI4iv QEGL_FN(glVertexAttribI4iv)
+#undef glVertexAttribI4ui
+#define glVertexAttribI4ui QEGL_FN(glVertexAttribI4ui)
+#undef glVertexAttribI4uiv
+#define glVertexAttribI4uiv QEGL_FN(glVertexAttribI4uiv)
+#undef glVertexAttribIPointer
+#define glVertexAttribIPointer QEGL_FN(glVertexAttribIPointer)
+#undef glVertexAttribPointerARB
+#define glVertexAttribPointerARB QEGL_FN(glVertexAttribPointerARB)
+#undef glVertexPointer
+#define glVertexPointer QEGL_FN(glVertexPointer)
+#undef glViewport
+#define glViewport QEGL_FN(glViewport)
+#undef glWaitSync
+#define glWaitSync QEGL_FN(glWaitSync)
+#undef glWindowPos2d
+#define glWindowPos2d QEGL_FN(glWindowPos2d)
+#undef glWindowPos2dv
+#define glWindowPos2dv QEGL_FN(glWindowPos2dv)
+#undef glWindowPos2f
+#define glWindowPos2f QEGL_FN(glWindowPos2f)
+#undef glWindowPos2fv
+#define glWindowPos2fv QEGL_FN(glWindowPos2fv)
+#undef glWindowPos2i
+#define glWindowPos2i QEGL_FN(glWindowPos2i)
+#undef glWindowPos2iv
+#define glWindowPos2iv QEGL_FN(glWindowPos2iv)
+#undef glWindowPos2s
+#define glWindowPos2s QEGL_FN(glWindowPos2s)
+#undef glWindowPos2sv
+#define glWindowPos2sv QEGL_FN(glWindowPos2sv)
+#undef glWindowPos3d
+#define glWindowPos3d QEGL_FN(glWindowPos3d)
+#undef glWindowPos3dv
+#define glWindowPos3dv QEGL_FN(glWindowPos3dv)
+#undef glWindowPos3f
+#define glWindowPos3f QEGL_FN(glWindowPos3f)
+#undef glWindowPos3fv
+#define glWindowPos3fv QEGL_FN(glWindowPos3fv)
+#undef glWindowPos3i
+#define glWindowPos3i QEGL_FN(glWindowPos3i)
+#undef glWindowPos3iv
+#define glWindowPos3iv QEGL_FN(glWindowPos3iv)
+#undef glWindowPos3s
+#define glWindowPos3s QEGL_FN(glWindowPos3s)
+#undef glWindowPos3sv
+#define glWindowPos3sv QEGL_FN(glWindowPos3sv)
+#undef wglCreateContext
+#define wglCreateContext QEGL_FN(wglCreateContext)
+#undef wglCreateContextAttribsARB
+#define wglCreateContextAttribsARB QEGL_FN(wglCreateContextAttribsARB)
+#undef wglDeleteContext
+#define wglDeleteContext QEGL_FN(wglDeleteContext)
+#undef wglGetProcAddress
+#define wglGetProcAddress QEGL_FN(wglGetProcAddress)
+#undef wglMakeCurrent
+#define wglMakeCurrent QEGL_FN(wglMakeCurrent)
+#undef wglShareLists
+#define wglShareLists QEGL_FN(wglShareLists)
+
+#ifdef QEGL_GLFN_DEFINE
+const char *const qegl_glfn_names[QEGL_GLFN_COUNT] = {
+    "glAccum",
+    "glActiveTexture",
+    "glAlphaFunc",
+    "glAreTexturesResident",
+    "glAttachObjectARB",
+    "glBegin",
+    "glBeginQuery",
+    "glBeginTransformFeedback",
+    "glBindAttribLocationARB",
+    "glBindBuffer",
+    "glBindBufferBase",
+    "glBindBufferRange",
+    "glBindFragDataLocation",
+    "glBindFramebuffer",
+    "glBindFramebufferEXT",
+    "glBindProgramARB",
+    "glBindRenderbufferEXT",
+    "glBindSampler",
+    "glBindTexture",
+    "glBindTransformFeedback",
+    "glBindVertexArray",
+    "glBitmap",
+    "glBlendColor",
+    "glBlendEquation",
+    "glBlendEquationSeparate",
+    "glBlendFunc",
+    "glBlendFuncSeparate",
+    "glBlitFramebuffer",
+    "glBufferData",
+    "glBufferSubData",
+    "glCallList",
+    "glCallLists",
+    "glCheckFramebufferStatus",
+    "glCheckFramebufferStatusEXT",
+    "glClear",
+    "glClearAccum",
+    "glClearBufferfi",
+    "glClearBufferfv",
+    "glClearBufferiv",
+    "glClearBufferuiv",
+    "glClearColor",
+    "glClearDepth",
+    "glClearIndex",
+    "glClearStencil",
+    "glClientActiveTexture",
+    "glClientWaitSync",
+    "glClipPlane",
+    "glColor3b",
+    "glColor3bv",
+    "glColor3d",
+    "glColor3dv",
+    "glColor3f",
+    "glColor3fv",
+    "glColor3i",
+    "glColor3iv",
+    "glColor3s",
+    "glColor3sv",
+    "glColor3ub",
+    "glColor3ubv",
+    "glColor3ui",
+    "glColor3uiv",
+    "glColor3us",
+    "glColor3usv",
+    "glColor4b",
+    "glColor4bv",
+    "glColor4d",
+    "glColor4dv",
+    "glColor4f",
+    "glColor4fv",
+    "glColor4i",
+    "glColor4iv",
+    "glColor4s",
+    "glColor4sv",
+    "glColor4ub",
+    "glColor4ubv",
+    "glColor4ui",
+    "glColor4uiv",
+    "glColor4us",
+    "glColor4usv",
+    "glColorMask",
+    "glColorMaski",
+    "glColorMaterial",
+    "glColorPointer",
+    "glColorTableParameterfv",
+    "glColorTableParameteriv",
+    "glCombinerInputNV",
+    "glCombinerOutputNV",
+    "glCombinerParameterfNV",
+    "glCombinerParameterfvNV",
+    "glCombinerParameteriNV",
+    "glCombinerParameterivNV",
+    "glCombinerStageParameterfvNV",
+    "glCompileShaderARB",
+    "glCompressedTexImage2D",
+    "glCompressedTexImage3D",
+    "glCompressedTexSubImage2D",
+    "glCompressedTexSubImage3D",
+    "glConvolutionParameterf",
+    "glConvolutionParameterfv",
+    "glConvolutionParameteri",
+    "glConvolutionParameteriv",
+    "glCopyBufferSubData",
+    "glCopyColorSubTable",
+    "glCopyColorTable",
+    "glCopyConvolutionFilter1D",
+    "glCopyConvolutionFilter2D",
+    "glCopyImageSubData",
+    "glCopyPixels",
+    "glCopyTexImage1D",
+    "glCopyTexImage2D",
+    "glCopyTexSubImage1D",
+    "glCopyTexSubImage2D",
+    "glCopyTexSubImage3D",
+    "glCreateMemoryObjectsEXT",
+    "glCreateProgramObjectARB",
+    "glCreateShaderObjectARB",
+    "glCullFace",
+    "glDebugMessageCallback",
+    "glDeleteBuffers",
+    "glDeleteFramebuffers",
+    "glDeleteFramebuffersEXT",
+    "glDeleteLists",
+    "glDeleteMemoryObjectsEXT",
+    "glDeleteObjectARB",
+    "glDeleteProgramsARB",
+    "glDeleteQueries",
+    "glDeleteRenderbuffers",
+    "glDeleteRenderbuffersEXT",
+    "glDeleteSamplers",
+    "glDeleteSemaphoresEXT",
+    "glDeleteSync",
+    "glDeleteTextures",
+    "glDeleteTransformFeedbacks",
+    "glDeleteVertexArrays",
+    "glDepthBoundsEXT",
+    "glDepthFunc",
+    "glDepthMask",
+    "glDepthRange",
+    "glDetachObjectARB",
+    "glDisable",
+    "glDisableClientState",
+    "glDisableVertexAttribArrayARB",
+    "glDisablei",
+    "glDrawArrays",
+    "glDrawArraysInstanced",
+    "glDrawBuffer",
+    "glDrawBuffers",
+    "glDrawElementsInstanced",
+    "glDrawPixels",
+    "glDrawRangeElements",
+    "glEdgeFlag",
+    "glEdgeFlagPointer",
+    "glEdgeFlagv",
+    "glEnable",
+    "glEnableClientState",
+    "glEnableVertexAttribArrayARB",
+    "glEnablei",
+    "glEnd",
+    "glEndList",
+    "glEndQuery",
+    "glEndTransformFeedback",
+    "glEvalCoord1d",
+    "glEvalCoord1dv",
+    "glEvalCoord1f",
+    "glEvalCoord1fv",
+    "glEvalCoord2d",
+    "glEvalCoord2dv",
+    "glEvalCoord2f",
+    "glEvalCoord2fv",
+    "glEvalMesh1",
+    "glEvalMesh2",
+    "glEvalPoint1",
+    "glEvalPoint2",
+    "glFenceSync",
+    "glFinalCombinerInputNV",
+    "glFinish",
+    "glFlush",
+    "glFogCoordPointer",
+    "glFogCoordd",
+    "glFogCoorddv",
+    "glFogCoordf",
+    "glFogCoordfv",
+    "glFogf",
+    "glFogfv",
+    "glFogi",
+    "glFogiv",
+    "glFramebufferRenderbuffer",
+    "glFramebufferRenderbufferEXT",
+    "glFramebufferTexture1DEXT",
+    "glFramebufferTexture2D",
+    "glFramebufferTexture2DEXT",
+    "glFramebufferTexture3DEXT",
+    "glFramebufferTextureLayer",
+    "glFrontFace",
+    "glFrustum",
+    "glGenFramebuffers",
+    "glGenSamplers",
+    "glGenSemaphoresEXT",
+    "glGenTransformFeedbacks",
+    "glGenVertexArrays",
+    "glGenerateMipmapEXT",
+    "glGetActiveAttribARB",
+    "glGetActiveUniformARB",
+    "glGetActiveUniformBlockName",
+    "glGetActiveUniformBlockiv",
+    "glGetActiveUniformsiv",
+    "glGetAttachedObjectsARB",
+    "glGetAttribLocationARB",
+    "glGetBooleani_v",
+    "glGetBooleanv",
+    "glGetBufferParameteri64v",
+    "glGetBufferParameteriv",
+    "glGetBufferSubData",
+    "glGetClipPlane",
+    "glGetColorTableParameterfv",
+    "glGetColorTableParameteriv",
+    "glGetCombinerInputParameterfvNV",
+    "glGetCombinerInputParameterivNV",
+    "glGetCombinerOutputParameterfvNV",
+    "glGetCombinerOutputParameterivNV",
+    "glGetCombinerStageParameterfvNV",
+    "glGetConvolutionParameterfv",
+    "glGetConvolutionParameteriv",
+    "glGetDoublev",
+    "glGetError",
+    "glGetFinalCombinerInputParameterfvNV",
+    "glGetFinalCombinerInputParameterivNV",
+    "glGetFloatv",
+    "glGetFragDataLocation",
+    "glGetFramebufferAttachmentParameteriv",
+    "glGetFramebufferAttachmentParameterivEXT",
+    "glGetHistogramParameterfv",
+    "glGetHistogramParameteriv",
+    "glGetInfoLogARB",
+    "glGetInteger64i_v",
+    "glGetInteger64v",
+    "glGetIntegeri_v",
+    "glGetIntegerv",
+    "glGetInternalformativ",
+    "glGetLightfv",
+    "glGetLightiv",
+    "glGetMaterialfv",
+    "glGetMaterialiv",
+    "glGetMinmaxParameterfv",
+    "glGetMinmaxParameteriv",
+    "glGetNamedFramebufferAttachmentParameteriv",
+    "glGetObjectParameterfvARB",
+    "glGetObjectParameterivARB",
+    "glGetProgramEnvParameterdvARB",
+    "glGetProgramEnvParameterfvARB",
+    "glGetProgramInfoLog",
+    "glGetProgramLocalParameterdvARB",
+    "glGetProgramLocalParameterfvARB",
+    "glGetProgramiv",
+    "glGetProgramivARB",
+    "glGetQueryObjecti64v",
+    "glGetQueryObjectiv",
+    "glGetQueryObjectui64v",
+    "glGetQueryObjectuiv",
+    "glGetQueryiv",
+    "glGetRenderbufferParameterivEXT",
+    "glGetSamplerParameterfv",
+    "glGetSamplerParameteriv",
+    "glGetShaderInfoLog",
+    "glGetShaderSourceARB",
+    "glGetShaderiv",
+    "glGetString",
+    "glGetStringi",
+    "glGetSynciv",
+    "glGetTexEnvfv",
+    "glGetTexEnviv",
+    "glGetTexGendv",
+    "glGetTexGenfv",
+    "glGetTexGeniv",
+    "glGetTexLevelParameterfv",
+    "glGetTexLevelParameteriv",
+    "glGetTexParameterfv",
+    "glGetTexParameteriv",
+    "glGetTransformFeedbackVarying",
+    "glGetUniformBlockIndex",
+    "glGetUniformIndices",
+    "glGetUniformLocationARB",
+    "glGetUniformfvARB",
+    "glGetUniformivARB",
+    "glGetUniformuiv",
+    "glGetUnsignedBytevEXT",
+    "glGetVertexAttribIiv",
+    "glGetVertexAttribIuiv",
+    "glGetVertexAttribdvARB",
+    "glGetVertexAttribfvARB",
+    "glGetVertexAttribivARB",
+    "glHint",
+    "glHistogram",
+    "glImportMemoryWin32HandleEXT",
+    "glImportSemaphoreWin32HandleEXT",
+    "glIndexMask",
+    "glIndexPointer",
+    "glIndexd",
+    "glIndexdv",
+    "glIndexf",
+    "glIndexfv",
+    "glIndexi",
+    "glIndexiv",
+    "glIndexs",
+    "glIndexsv",
+    "glIndexub",
+    "glIndexubv",
+    "glInitNames",
+    "glInvalidateFramebuffer",
+    "glInvalidateSubFramebuffer",
+    "glIsBuffer",
+    "glIsEnabled",
+    "glIsEnabledi",
+    "glIsFramebufferEXT",
+    "glIsList",
+    "glIsProgram",
+    "glIsProgramARB",
+    "glIsQuery",
+    "glIsRenderbufferEXT",
+    "glIsSampler",
+    "glIsShader",
+    "glIsSync",
+    "glIsTexture",
+    "glLightModelf",
+    "glLightModelfv",
+    "glLightModeli",
+    "glLightModeliv",
+    "glLightf",
+    "glLightfv",
+    "glLighti",
+    "glLightiv",
+    "glLineStipple",
+    "glLineWidth",
+    "glLinkProgramARB",
+    "glListBase",
+    "glLoadIdentity",
+    "glLoadMatrixd",
+    "glLoadMatrixf",
+    "glLoadName",
+    "glLoadTransposeMatrixd",
+    "glLoadTransposeMatrixf",
+    "glLogicOp",
+    "glMapBufferRange",
+    "glMapGrid1d",
+    "glMapGrid1f",
+    "glMapGrid2d",
+    "glMapGrid2f",
+    "glMaterialf",
+    "glMaterialfv",
+    "glMateriali",
+    "glMaterialiv",
+    "glMatrixMode",
+    "glMemoryObjectParameterivEXT",
+    "glMinmax",
+    "glMultMatrixd",
+    "glMultMatrixf",
+    "glMultTransposeMatrixd",
+    "glMultTransposeMatrixf",
+    "glMultiTexCoord1d",
+    "glMultiTexCoord1dv",
+    "glMultiTexCoord1f",
+    "glMultiTexCoord1fv",
+    "glMultiTexCoord1i",
+    "glMultiTexCoord1iv",
+    "glMultiTexCoord1s",
+    "glMultiTexCoord1sv",
+    "glMultiTexCoord2d",
+    "glMultiTexCoord2dv",
+    "glMultiTexCoord2f",
+    "glMultiTexCoord2fv",
+    "glMultiTexCoord2i",
+    "glMultiTexCoord2iv",
+    "glMultiTexCoord2s",
+    "glMultiTexCoord2sv",
+    "glMultiTexCoord3d",
+    "glMultiTexCoord3dv",
+    "glMultiTexCoord3f",
+    "glMultiTexCoord3fv",
+    "glMultiTexCoord3i",
+    "glMultiTexCoord3iv",
+    "glMultiTexCoord3s",
+    "glMultiTexCoord3sv",
+    "glMultiTexCoord4d",
+    "glMultiTexCoord4dv",
+    "glMultiTexCoord4f",
+    "glMultiTexCoord4fv",
+    "glMultiTexCoord4i",
+    "glMultiTexCoord4iv",
+    "glMultiTexCoord4s",
+    "glMultiTexCoord4sv",
+    "glNewList",
+    "glNormal3b",
+    "glNormal3bv",
+    "glNormal3d",
+    "glNormal3dv",
+    "glNormal3f",
+    "glNormal3fv",
+    "glNormal3i",
+    "glNormal3iv",
+    "glNormal3s",
+    "glNormal3sv",
+    "glNormalPointer",
+    "glOrtho",
+    "glPassThrough",
+    "glPauseTransformFeedback",
+    "glPixelStorei",
+    "glPixelTransferf",
+    "glPixelTransferi",
+    "glPixelZoom",
+    "glPointParameterf",
+    "glPointParameterfv",
+    "glPointParameteri",
+    "glPointParameteriv",
+    "glPointSize",
+    "glPolygonMode",
+    "glPolygonOffset",
+    "glPolygonStipple",
+    "glPopAttrib",
+    "glPopMatrix",
+    "glPopName",
+    "glPrimitiveRestartIndex",
+    "glPrioritizeTextures",
+    "glProgramEnvParameter4dARB",
+    "glProgramEnvParameter4dvARB",
+    "glProgramEnvParameter4fARB",
+    "glProgramEnvParameter4fvARB",
+    "glProgramEnvParameters4fvEXT",
+    "glProgramLocalParameter4dARB",
+    "glProgramLocalParameter4dvARB",
+    "glProgramLocalParameter4fARB",
+    "glProgramLocalParameter4fvARB",
+    "glProgramLocalParameters4fvEXT",
+    "glProgramStringARB",
+    "glPushAttrib",
+    "glPushMatrix",
+    "glPushName",
+    "glQueryCounter",
+    "glRasterPos2d",
+    "glRasterPos2dv",
+    "glRasterPos2f",
+    "glRasterPos2fv",
+    "glRasterPos2i",
+    "glRasterPos2iv",
+    "glRasterPos2s",
+    "glRasterPos2sv",
+    "glRasterPos3d",
+    "glRasterPos3dv",
+    "glRasterPos3f",
+    "glRasterPos3fv",
+    "glRasterPos3i",
+    "glRasterPos3iv",
+    "glRasterPos3s",
+    "glRasterPos3sv",
+    "glRasterPos4d",
+    "glRasterPos4dv",
+    "glRasterPos4f",
+    "glRasterPos4fv",
+    "glRasterPos4i",
+    "glRasterPos4iv",
+    "glRasterPos4s",
+    "glRasterPos4sv",
+    "glReadBuffer",
+    "glReadPixels",
+    "glRectd",
+    "glRectdv",
+    "glRectf",
+    "glRectfv",
+    "glRecti",
+    "glRectiv",
+    "glRects",
+    "glRectsv",
+    "glRenderMode",
+    "glRenderbufferStorage",
+    "glRenderbufferStorageEXT",
+    "glRenderbufferStorageMultisample",
+    "glResetHistogram",
+    "glResetMinmax",
+    "glResumeTransformFeedback",
+    "glRotated",
+    "glRotatef",
+    "glSampleCoverage",
+    "glSamplerParameterf",
+    "glSamplerParameterfv",
+    "glSamplerParameteri",
+    "glSamplerParameteriv",
+    "glScaled",
+    "glScalef",
+    "glScissor",
+    "glSecondaryColor3b",
+    "glSecondaryColor3bv",
+    "glSecondaryColor3d",
+    "glSecondaryColor3dv",
+    "glSecondaryColor3f",
+    "glSecondaryColor3fv",
+    "glSecondaryColor3i",
+    "glSecondaryColor3iv",
+    "glSecondaryColor3s",
+    "glSecondaryColor3sv",
+    "glSecondaryColor3ub",
+    "glSecondaryColor3ubv",
+    "glSecondaryColor3ui",
+    "glSecondaryColor3uiv",
+    "glSecondaryColor3us",
+    "glSecondaryColor3usv",
+    "glSecondaryColorPointer",
+    "glSemaphoreParameterui64vEXT",
+    "glShadeModel",
+    "glShaderSourceARB",
+    "glSignalSemaphoreEXT",
+    "glStencilFunc",
+    "glStencilFuncSeparate",
+    "glStencilMask",
+    "glStencilMaskSeparate",
+    "glStencilOp",
+    "glStencilOpSeparate",
+    "glTexCoord1d",
+    "glTexCoord1dv",
+    "glTexCoord1f",
+    "glTexCoord1fv",
+    "glTexCoord1i",
+    "glTexCoord1iv",
+    "glTexCoord1s",
+    "glTexCoord1sv",
+    "glTexCoord2d",
+    "glTexCoord2dv",
+    "glTexCoord2f",
+    "glTexCoord2fv",
+    "glTexCoord2i",
+    "glTexCoord2iv",
+    "glTexCoord2s",
+    "glTexCoord2sv",
+    "glTexCoord3d",
+    "glTexCoord3dv",
+    "glTexCoord3f",
+    "glTexCoord3fv",
+    "glTexCoord3i",
+    "glTexCoord3iv",
+    "glTexCoord3s",
+    "glTexCoord3sv",
+    "glTexCoord4d",
+    "glTexCoord4dv",
+    "glTexCoord4f",
+    "glTexCoord4fv",
+    "glTexCoord4i",
+    "glTexCoord4iv",
+    "glTexCoord4s",
+    "glTexCoord4sv",
+    "glTexCoordPointer",
+    "glTexEnvf",
+    "glTexEnvfv",
+    "glTexEnvi",
+    "glTexEnviv",
+    "glTexGend",
+    "glTexGendv",
+    "glTexGenf",
+    "glTexGenfv",
+    "glTexGeni",
+    "glTexGeniv",
+    "glTexImage1D",
+    "glTexImage2D",
+    "glTexImage3D",
+    "glTexParameterf",
+    "glTexParameterfv",
+    "glTexParameteri",
+    "glTexParameteriv",
+    "glTexStorage2D",
+    "glTexStorage3D",
+    "glTexStorageMem2DEXT",
+    "glTexSubImage1D",
+    "glTexSubImage2D",
+    "glTexSubImage3D",
+    "glTransformFeedbackVaryings",
+    "glTranslated",
+    "glTranslatef",
+    "glUniform1fARB",
+    "glUniform1fvARB",
+    "glUniform1iARB",
+    "glUniform1ivARB",
+    "glUniform1ui",
+    "glUniform1uiv",
+    "glUniform2fARB",
+    "glUniform2fvARB",
+    "glUniform2iARB",
+    "glUniform2ivARB",
+    "glUniform2ui",
+    "glUniform2uiv",
+    "glUniform3fARB",
+    "glUniform3fvARB",
+    "glUniform3iARB",
+    "glUniform3ivARB",
+    "glUniform3ui",
+    "glUniform3uiv",
+    "glUniform4fARB",
+    "glUniform4fvARB",
+    "glUniform4iARB",
+    "glUniform4ivARB",
+    "glUniform4ui",
+    "glUniform4uiv",
+    "glUniformBlockBinding",
+    "glUniformMatrix2fvARB",
+    "glUniformMatrix2x3fv",
+    "glUniformMatrix2x4fv",
+    "glUniformMatrix3fvARB",
+    "glUniformMatrix3x2fv",
+    "glUniformMatrix3x4fv",
+    "glUniformMatrix4fvARB",
+    "glUniformMatrix4x2fv",
+    "glUniformMatrix4x3fv",
+    "glUnmapBuffer",
+    "glUseProgramObjectARB",
+    "glValidateProgramARB",
+    "glVertex2d",
+    "glVertex2dv",
+    "glVertex2f",
+    "glVertex2fv",
+    "glVertex2i",
+    "glVertex2iv",
+    "glVertex2s",
+    "glVertex2sv",
+    "glVertex3d",
+    "glVertex3dv",
+    "glVertex3f",
+    "glVertex3fv",
+    "glVertex3i",
+    "glVertex3iv",
+    "glVertex3s",
+    "glVertex3sv",
+    "glVertex4d",
+    "glVertex4dv",
+    "glVertex4f",
+    "glVertex4fv",
+    "glVertex4i",
+    "glVertex4iv",
+    "glVertex4s",
+    "glVertex4sv",
+    "glVertexAttrib1dARB",
+    "glVertexAttrib1dvARB",
+    "glVertexAttrib1fARB",
+    "glVertexAttrib1fvARB",
+    "glVertexAttrib1sARB",
+    "glVertexAttrib1svARB",
+    "glVertexAttrib2dARB",
+    "glVertexAttrib2dvARB",
+    "glVertexAttrib2fARB",
+    "glVertexAttrib2fvARB",
+    "glVertexAttrib2sARB",
+    "glVertexAttrib2svARB",
+    "glVertexAttrib3dARB",
+    "glVertexAttrib3dvARB",
+    "glVertexAttrib3fARB",
+    "glVertexAttrib3fvARB",
+    "glVertexAttrib3sARB",
+    "glVertexAttrib3svARB",
+    "glVertexAttrib4NbvARB",
+    "glVertexAttrib4NivARB",
+    "glVertexAttrib4NsvARB",
+    "glVertexAttrib4NubARB",
+    "glVertexAttrib4NubvARB",
+    "glVertexAttrib4NuivARB",
+    "glVertexAttrib4NusvARB",
+    "glVertexAttrib4bvARB",
+    "glVertexAttrib4dARB",
+    "glVertexAttrib4dvARB",
+    "glVertexAttrib4fARB",
+    "glVertexAttrib4fvARB",
+    "glVertexAttrib4ivARB",
+    "glVertexAttrib4sARB",
+    "glVertexAttrib4svARB",
+    "glVertexAttrib4ubvARB",
+    "glVertexAttrib4uivARB",
+    "glVertexAttrib4usvARB",
+    "glVertexAttribDivisor",
+    "glVertexAttribI4i",
+    "glVertexAttribI4iv",
+    "glVertexAttribI4ui",
+    "glVertexAttribI4uiv",
+    "glVertexAttribIPointer",
+    "glVertexAttribPointerARB",
+    "glVertexPointer",
+    "glViewport",
+    "glWaitSync",
+    "glWindowPos2d",
+    "glWindowPos2dv",
+    "glWindowPos2f",
+    "glWindowPos2fv",
+    "glWindowPos2i",
+    "glWindowPos2iv",
+    "glWindowPos2s",
+    "glWindowPos2sv",
+    "glWindowPos3d",
+    "glWindowPos3dv",
+    "glWindowPos3f",
+    "glWindowPos3fv",
+    "glWindowPos3i",
+    "glWindowPos3iv",
+    "glWindowPos3s",
+    "glWindowPos3sv",
+    "wglCreateContext",
+    "wglCreateContextAttribsARB",
+    "wglDeleteContext",
+    "wglGetProcAddress",
+    "wglMakeCurrent",
+    "wglShareLists",
+};
+#endif
+
+#endif
diff --git a/hw/display/qegl-index.h b/hw/display/qegl-index.h
new file mode 100644
index 0000000..3b37eee
--- /dev/null
+++ b/hw/display/qegl-index.h
@@ -0,0 +1,690 @@
+/* Generated by gen/gen_gl.py - GLIFunctionDispatch indices */
+#ifndef QEGL_INDEX_H
+#define QEGL_INDEX_H
+#define QI_accum 0
+#define QI_alpha_func 1
+#define QI_are_textures_resident 2
+#define QI_array_element 3
+#define QI_begin 4
+#define QI_bind_texture 5
+#define QI_bitmap 6
+#define QI_blend_func 7
+#define QI_call_list 8
+#define QI_call_lists 9
+#define QI_clear 10
+#define QI_clear_accum 11
+#define QI_clear_color 12
+#define QI_clear_depth 13
+#define QI_clear_index 14
+#define QI_clear_stencil 15
+#define QI_clip_plane 16
+#define QI_color3b 17
+#define QI_color3bv 18
+#define QI_color3d 19
+#define QI_color3dv 20
+#define QI_color3f 21
+#define QI_color3fv 22
+#define QI_color3i 23
+#define QI_color3iv 24
+#define QI_color3s 25
+#define QI_color3sv 26
+#define QI_color3ub 27
+#define QI_color3ubv 28
+#define QI_color3ui 29
+#define QI_color3uiv 30
+#define QI_color3us 31
+#define QI_color3usv 32
+#define QI_color4b 33
+#define QI_color4bv 34
+#define QI_color4d 35
+#define QI_color4dv 36
+#define QI_color4f 37
+#define QI_color4fv 38
+#define QI_color4i 39
+#define QI_color4iv 40
+#define QI_color4s 41
+#define QI_color4sv 42
+#define QI_color4ub 43
+#define QI_color4ubv 44
+#define QI_color4ui 45
+#define QI_color4uiv 46
+#define QI_color4us 47
+#define QI_color4usv 48
+#define QI_color_mask 49
+#define QI_color_material 50
+#define QI_color_pointer 51
+#define QI_copy_pixels 52
+#define QI_copy_tex_image1D 53
+#define QI_copy_tex_image2D 54
+#define QI_copy_tex_sub_image1D 55
+#define QI_copy_tex_sub_image2D 56
+#define QI_cull_face 57
+#define QI_delete_lists 58
+#define QI_delete_textures 59
+#define QI_depth_func 60
+#define QI_depth_mask 61
+#define QI_depth_range 62
+#define QI_disable 63
+#define QI_disable_client_state 64
+#define QI_draw_arrays 65
+#define QI_draw_buffer 66
+#define QI_draw_elements 67
+#define QI_draw_pixels 68
+#define QI_edge_flag 69
+#define QI_edge_flag_pointer 70
+#define QI_edge_flagv 71
+#define QI_enable 72
+#define QI_enable_client_state 73
+#define QI_end 74
+#define QI_end_list 75
+#define QI_eval_coord1d 76
+#define QI_eval_coord1dv 77
+#define QI_eval_coord1f 78
+#define QI_eval_coord1fv 79
+#define QI_eval_coord2d 80
+#define QI_eval_coord2dv 81
+#define QI_eval_coord2f 82
+#define QI_eval_coord2fv 83
+#define QI_eval_mesh1 84
+#define QI_eval_mesh2 85
+#define QI_eval_point1 86
+#define QI_eval_point2 87
+#define QI_feedback_buffer 88
+#define QI_finish 89
+#define QI_flush 90
+#define QI_fogf 91
+#define QI_fogfv 92
+#define QI_fogi 93
+#define QI_fogiv 94
+#define QI_front_face 95
+#define QI_frustum 96
+#define QI_gen_lists 97
+#define QI_gen_textures 98
+#define QI_get_booleanv 99
+#define QI_get_clip_plane 100
+#define QI_get_doublev 101
+#define QI_get_error 102
+#define QI_get_floatv 103
+#define QI_get_integerv 104
+#define QI_get_lightfv 105
+#define QI_get_lightiv 106
+#define QI_get_mapdv 107
+#define QI_get_mapfv 108
+#define QI_get_mapiv 109
+#define QI_get_materialfv 110
+#define QI_get_materialiv 111
+#define QI_get_pixel_mapfv 112
+#define QI_get_pixel_mapuiv 113
+#define QI_get_pixel_mapusv 114
+#define QI_get_pointerv 115
+#define QI_get_polygon_stipple 116
+#define QI_get_string 117
+#define QI_get_tex_envfv 118
+#define QI_get_tex_enviv 119
+#define QI_get_tex_gendv 120
+#define QI_get_tex_genfv 121
+#define QI_get_tex_geniv 122
+#define QI_get_tex_image 123
+#define QI_get_tex_level_parameterfv 124
+#define QI_get_tex_level_parameteriv 125
+#define QI_get_tex_parameterfv 126
+#define QI_get_tex_parameteriv 127
+#define QI_hint 128
+#define QI_index_mask 129
+#define QI_index_pointer 130
+#define QI_indexd 131
+#define QI_indexdv 132
+#define QI_indexf 133
+#define QI_indexfv 134
+#define QI_indexi 135
+#define QI_indexiv 136
+#define QI_indexs 137
+#define QI_indexsv 138
+#define QI_indexub 139
+#define QI_indexubv 140
+#define QI_init_names 141
+#define QI_interleaved_arrays 142
+#define QI_is_enabled 143
+#define QI_is_list 144
+#define QI_is_texture 145
+#define QI_light_modelf 146
+#define QI_light_modelfv 147
+#define QI_light_modeli 148
+#define QI_light_modeliv 149
+#define QI_lightf 150
+#define QI_lightfv 151
+#define QI_lighti 152
+#define QI_lightiv 153
+#define QI_line_stipple 154
+#define QI_line_width 155
+#define QI_list_base 156
+#define QI_load_identity 157
+#define QI_load_matrixd 158
+#define QI_load_matrixf 159
+#define QI_load_name 160
+#define QI_logic_op 161
+#define QI_map1d 162
+#define QI_map1f 163
+#define QI_map2d 164
+#define QI_map2f 165
+#define QI_map_grid1d 166
+#define QI_map_grid1f 167
+#define QI_map_grid2d 168
+#define QI_map_grid2f 169
+#define QI_materialf 170
+#define QI_materialfv 171
+#define QI_materiali 172
+#define QI_materialiv 173
+#define QI_matrix_mode 174
+#define QI_mult_matrixd 175
+#define QI_mult_matrixf 176
+#define QI_new_list 177
+#define QI_normal3b 178
+#define QI_normal3bv 179
+#define QI_normal3d 180
+#define QI_normal3dv 181
+#define QI_normal3f 182
+#define QI_normal3fv 183
+#define QI_normal3i 184
+#define QI_normal3iv 185
+#define QI_normal3s 186
+#define QI_normal3sv 187
+#define QI_normal_pointer 188
+#define QI_ortho 189
+#define QI_pass_through 190
+#define QI_pixel_mapfv 191
+#define QI_pixel_mapuiv 192
+#define QI_pixel_mapusv 193
+#define QI_pixel_storef 194
+#define QI_pixel_storei 195
+#define QI_pixel_transferf 196
+#define QI_pixel_transferi 197
+#define QI_pixel_zoom 198
+#define QI_point_size 199
+#define QI_polygon_mode 200
+#define QI_polygon_offset 201
+#define QI_polygon_stipple 202
+#define QI_pop_attrib 203
+#define QI_pop_client_attrib 204
+#define QI_pop_matrix 205
+#define QI_pop_name 206
+#define QI_prioritize_textures 207
+#define QI_push_attrib 208
+#define QI_push_client_attrib 209
+#define QI_push_matrix 210
+#define QI_push_name 211
+#define QI_raster_pos2d 212
+#define QI_raster_pos2dv 213
+#define QI_raster_pos2f 214
+#define QI_raster_pos2fv 215
+#define QI_raster_pos2i 216
+#define QI_raster_pos2iv 217
+#define QI_raster_pos2s 218
+#define QI_raster_pos2sv 219
+#define QI_raster_pos3d 220
+#define QI_raster_pos3dv 221
+#define QI_raster_pos3f 222
+#define QI_raster_pos3fv 223
+#define QI_raster_pos3i 224
+#define QI_raster_pos3iv 225
+#define QI_raster_pos3s 226
+#define QI_raster_pos3sv 227
+#define QI_raster_pos4d 228
+#define QI_raster_pos4dv 229
+#define QI_raster_pos4f 230
+#define QI_raster_pos4fv 231
+#define QI_raster_pos4i 232
+#define QI_raster_pos4iv 233
+#define QI_raster_pos4s 234
+#define QI_raster_pos4sv 235
+#define QI_read_buffer 236
+#define QI_read_pixels 237
+#define QI_rectd 238
+#define QI_rectdv 239
+#define QI_rectf 240
+#define QI_rectfv 241
+#define QI_recti 242
+#define QI_rectiv 243
+#define QI_rects 244
+#define QI_rectsv 245
+#define QI_render_mode 246
+#define QI_rotated 247
+#define QI_rotatef 248
+#define QI_scaled 249
+#define QI_scalef 250
+#define QI_scissor 251
+#define QI_select_buffer 252
+#define QI_shade_model 253
+#define QI_stencil_func 254
+#define QI_stencil_mask 255
+#define QI_stencil_op 256
+#define QI_tex_coord1d 257
+#define QI_tex_coord1dv 258
+#define QI_tex_coord1f 259
+#define QI_tex_coord1fv 260
+#define QI_tex_coord1i 261
+#define QI_tex_coord1iv 262
+#define QI_tex_coord1s 263
+#define QI_tex_coord1sv 264
+#define QI_tex_coord2d 265
+#define QI_tex_coord2dv 266
+#define QI_tex_coord2f 267
+#define QI_tex_coord2fv 268
+#define QI_tex_coord2i 269
+#define QI_tex_coord2iv 270
+#define QI_tex_coord2s 271
+#define QI_tex_coord2sv 272
+#define QI_tex_coord3d 273
+#define QI_tex_coord3dv 274
+#define QI_tex_coord3f 275
+#define QI_tex_coord3fv 276
+#define QI_tex_coord3i 277
+#define QI_tex_coord3iv 278
+#define QI_tex_coord3s 279
+#define QI_tex_coord3sv 280
+#define QI_tex_coord4d 281
+#define QI_tex_coord4dv 282
+#define QI_tex_coord4f 283
+#define QI_tex_coord4fv 284
+#define QI_tex_coord4i 285
+#define QI_tex_coord4iv 286
+#define QI_tex_coord4s 287
+#define QI_tex_coord4sv 288
+#define QI_tex_coord_pointer 289
+#define QI_tex_envf 290
+#define QI_tex_envfv 291
+#define QI_tex_envi 292
+#define QI_tex_enviv 293
+#define QI_tex_gend 294
+#define QI_tex_gendv 295
+#define QI_tex_genf 296
+#define QI_tex_genfv 297
+#define QI_tex_geni 298
+#define QI_tex_geniv 299
+#define QI_tex_image1D 300
+#define QI_tex_image2D 301
+#define QI_tex_parameterf 302
+#define QI_tex_parameterfv 303
+#define QI_tex_parameteri 304
+#define QI_tex_parameteriv 305
+#define QI_tex_sub_image1D 306
+#define QI_tex_sub_image2D 307
+#define QI_translated 308
+#define QI_translatef 309
+#define QI_vertex2d 310
+#define QI_vertex2dv 311
+#define QI_vertex2f 312
+#define QI_vertex2fv 313
+#define QI_vertex2i 314
+#define QI_vertex2iv 315
+#define QI_vertex2s 316
+#define QI_vertex2sv 317
+#define QI_vertex3d 318
+#define QI_vertex3dv 319
+#define QI_vertex3f 320
+#define QI_vertex3fv 321
+#define QI_vertex3i 322
+#define QI_vertex3iv 323
+#define QI_vertex3s 324
+#define QI_vertex3sv 325
+#define QI_vertex4d 326
+#define QI_vertex4dv 327
+#define QI_vertex4f 328
+#define QI_vertex4fv 329
+#define QI_vertex4i 330
+#define QI_vertex4iv 331
+#define QI_vertex4s 332
+#define QI_vertex4sv 333
+#define QI_vertex_pointer 334
+#define QI_viewport 335
+#define QI_blend_func_separate 336
+#define QI_blend_color 337
+#define QI_blend_equation 338
+#define QI_lock_arrays_EXT 339
+#define QI_unlock_arrays_EXT 340
+#define QI_client_active_texture 341
+#define QI_active_texture 342
+#define QI_multi_tex_coord1d 343
+#define QI_multi_tex_coord1dv 344
+#define QI_multi_tex_coord1f 345
+#define QI_multi_tex_coord1fv 346
+#define QI_multi_tex_coord1i 347
+#define QI_multi_tex_coord1iv 348
+#define QI_multi_tex_coord1s 349
+#define QI_multi_tex_coord1sv 350
+#define QI_multi_tex_coord2d 351
+#define QI_multi_tex_coord2dv 352
+#define QI_multi_tex_coord2f 353
+#define QI_multi_tex_coord2fv 354
+#define QI_multi_tex_coord2i 355
+#define QI_multi_tex_coord2iv 356
+#define QI_multi_tex_coord2s 357
+#define QI_multi_tex_coord2sv 358
+#define QI_multi_tex_coord3d 359
+#define QI_multi_tex_coord3dv 360
+#define QI_multi_tex_coord3f 361
+#define QI_multi_tex_coord3fv 362
+#define QI_multi_tex_coord3i 363
+#define QI_multi_tex_coord3iv 364
+#define QI_multi_tex_coord3s 365
+#define QI_multi_tex_coord3sv 366
+#define QI_multi_tex_coord4d 367
+#define QI_multi_tex_coord4dv 368
+#define QI_multi_tex_coord4f 369
+#define QI_multi_tex_coord4fv 370
+#define QI_multi_tex_coord4i 371
+#define QI_multi_tex_coord4iv 372
+#define QI_multi_tex_coord4s 373
+#define QI_multi_tex_coord4sv 374
+#define QI_load_transpose_matrixd 375
+#define QI_load_transpose_matrixf 376
+#define QI_mult_transpose_matrixd 377
+#define QI_mult_transpose_matrixf 378
+#define QI_compressed_tex_image3D 379
+#define QI_compressed_tex_image2D 380
+#define QI_compressed_tex_image1D 381
+#define QI_compressed_tex_sub_image3D 382
+#define QI_compressed_tex_sub_image2D 383
+#define QI_compressed_tex_sub_image1D 384
+#define QI_get_compressed_tex_image 385
+#define QI_secondary_color3b 386
+#define QI_secondary_color3bv 387
+#define QI_secondary_color3d 388
+#define QI_secondary_color3dv 389
+#define QI_secondary_color3f 390
+#define QI_secondary_color3fv 391
+#define QI_secondary_color3i 392
+#define QI_secondary_color3iv 393
+#define QI_secondary_color3s 394
+#define QI_secondary_color3sv 395
+#define QI_secondary_color3ub 396
+#define QI_secondary_color3ubv 397
+#define QI_secondary_color3ui 398
+#define QI_secondary_color3uiv 399
+#define QI_secondary_color3us 400
+#define QI_secondary_color3usv 401
+#define QI_secondary_color_pointer 402
+#define QI_vertex_array_range_EXT 403
+#define QI_flush_vertex_array_range_EXT 404
+#define QI_draw_range_elements 405
+#define QI_color_table 406
+#define QI_color_table_parameterfv 407
+#define QI_color_table_parameteriv 408
+#define QI_copy_color_table 409
+#define QI_get_color_table 410
+#define QI_get_color_table_parameterfv 411
+#define QI_get_color_table_parameteriv 412
+#define QI_color_sub_table 413
+#define QI_copy_color_sub_table 414
+#define QI_convolution_filter1D 415
+#define QI_convolution_filter2D 416
+#define QI_convolution_parameterf 417
+#define QI_convolution_parameterfv 418
+#define QI_convolution_parameteri 419
+#define QI_convolution_parameteriv 420
+#define QI_copy_convolution_filter1D 421
+#define QI_copy_convolution_filter2D 422
+#define QI_get_convolution_filter 423
+#define QI_get_convolution_parameterfv 424
+#define QI_get_convolution_parameteriv 425
+#define QI_get_separable_filter 426
+#define QI_separable_filter2D 427
+#define QI_get_histogram 428
+#define QI_get_histogram_parameterfv 429
+#define QI_get_histogram_parameteriv 430
+#define QI_get_minmax 431
+#define QI_get_minmax_parameterfv 432
+#define QI_get_minmax_parameteriv 433
+#define QI_histogram 434
+#define QI_minmax 435
+#define QI_reset_histogram 436
+#define QI_reset_minmax 437
+#define QI_tex_image3D 438
+#define QI_tex_sub_image3D 439
+#define QI_copy_tex_sub_image3D 440
+#define QI_combiner_parameterfv_NV 441
+#define QI_combiner_parameterf_NV 442
+#define QI_combiner_parameteriv_NV 443
+#define QI_combiner_parameteri_NV 444
+#define QI_combiner_input_NV 445
+#define QI_combiner_output_NV 446
+#define QI_final_combiner_input_NV 447
+#define QI_get_combiner_input_parameterfv_NV 448
+#define QI_get_combiner_input_parameteriv_NV 449
+#define QI_get_combiner_output_parameterfv_NV 450
+#define QI_get_combiner_output_parameteriv_NV 451
+#define QI_get_final_combiner_input_parameterfv_NV 452
+#define QI_get_final_combiner_input_parameteriv_NV 453
+#define QI_combiner_stage_parameterfv_NV 454
+#define QI_get_combiner_stage_parameterfv_NV 455
+#define QI_texture_range_APPLE 456
+#define QI_get_tex_parameter_pointerv_APPLE 457
+#define QI_blend_equation_separate_EXT 458
+#define QI_sample_coverage 459
+#define QI_sample_pass 460
+#define QI_pn_trianglesi_ATI 461
+#define QI_pn_trianglesf_ATI 462
+#define QI_gen_fences_APPLE 463
+#define QI_delete_fences_APPLE 464
+#define QI_set_fence_APPLE 465
+#define QI_is_fence_APPLE 466
+#define QI_test_fence_APPLE 467
+#define QI_finish_fence_APPLE 468
+#define QI_test_object_APPLE 469
+#define QI_finish_object_APPLE 470
+#define QI_bind_program_ARB 471
+#define QI_delete_programs_ARB 472
+#define QI_gen_programs_ARB 473
+#define QI_is_program_ARB 474
+#define QI_vertex_attrib1s_ARB 475
+#define QI_vertex_attrib1f_ARB 476
+#define QI_vertex_attrib1d_ARB 477
+#define QI_vertex_attrib2s_ARB 478
+#define QI_vertex_attrib2f_ARB 479
+#define QI_vertex_attrib2d_ARB 480
+#define QI_vertex_attrib3s_ARB 481
+#define QI_vertex_attrib3f_ARB 482
+#define QI_vertex_attrib3d_ARB 483
+#define QI_vertex_attrib4s_ARB 484
+#define QI_vertex_attrib4f_ARB 485
+#define QI_vertex_attrib4d_ARB 486
+#define QI_vertex_attrib4Nub_ARB 487
+#define QI_vertex_attrib1sv_ARB 488
+#define QI_vertex_attrib1fv_ARB 489
+#define QI_vertex_attrib1dv_ARB 490
+#define QI_vertex_attrib2sv_ARB 491
+#define QI_vertex_attrib2fv_ARB 492
+#define QI_vertex_attrib2dv_ARB 493
+#define QI_vertex_attrib3sv_ARB 494
+#define QI_vertex_attrib3fv_ARB 495
+#define QI_vertex_attrib3dv_ARB 496
+#define QI_vertex_attrib4bv_ARB 497
+#define QI_vertex_attrib4sv_ARB 498
+#define QI_vertex_attrib4iv_ARB 499
+#define QI_vertex_attrib4ubv_ARB 500
+#define QI_vertex_attrib4usv_ARB 501
+#define QI_vertex_attrib4uiv_ARB 502
+#define QI_vertex_attrib4fv_ARB 503
+#define QI_vertex_attrib4dv_ARB 504
+#define QI_vertex_attrib4Nbv_ARB 505
+#define QI_vertex_attrib4Nsv_ARB 506
+#define QI_vertex_attrib4Niv_ARB 507
+#define QI_vertex_attrib4Nubv_ARB 508
+#define QI_vertex_attrib4Nusv_ARB 509
+#define QI_vertex_attrib4Nuiv_ARB 510
+#define QI_vertex_attrib_pointer_ARB 511
+#define QI_enable_vertex_attrib_array_ARB 512
+#define QI_disable_vertex_attrib_array_ARB 513
+#define QI_get_vertex_attribdv_ARB 514
+#define QI_get_vertex_attribfv_ARB 515
+#define QI_get_vertex_attribiv_ARB 516
+#define QI_get_vertex_attrib_pointerv_ARB 517
+#define QI_program_env_parameter4d_ARB 518
+#define QI_program_env_parameter4dv_ARB 519
+#define QI_program_env_parameter4f_ARB 520
+#define QI_program_env_parameter4fv_ARB 521
+#define QI_program_local_parameter4d_ARB 522
+#define QI_program_local_parameter4dv_ARB 523
+#define QI_program_local_parameter4f_ARB 524
+#define QI_program_local_parameter4fv_ARB 525
+#define QI_get_program_env_parameterdv_ARB 526
+#define QI_get_program_env_parameterfv_ARB 527
+#define QI_get_program_local_parameterdv_ARB 528
+#define QI_get_program_local_parameterfv_ARB 529
+#define QI_program_string_ARB 530
+#define QI_get_program_string_ARB 531
+#define QI_get_programiv_ARB 532
+#define QI_enable_vertex_attrib_ARB 533
+#define QI_disable_vertex_attrib_ARB 534
+#define QI_is_vertex_attrib_enabled_ARB 535
+#define QI_map_vertex_attrib1d_ARB 536
+#define QI_map_vertex_attrib1f_ARB 537
+#define QI_map_vertex_attrib2d_ARB 538
+#define QI_map_vertex_attrib2f_ARB 539
+#define QI_point_parameterf 540
+#define QI_point_parameterfv 541
+#define QI_point_parameteri 542
+#define QI_point_parameteriv 543
+#define QI_fog_coordf 544
+#define QI_fog_coordfv 545
+#define QI_fog_coordd 546
+#define QI_fog_coorddv 547
+#define QI_fog_coord_pointer 548
+#define QI_vertex_array_parameteri_EXT 549
+#define QI_bind_vertex_array_EXT 550
+#define QI_delete_vertex_arrays_EXT 551
+#define QI_gen_vertex_arrays_EXT 552
+#define QI_is_vertex_array_EXT 553
+#define QI_element_pointer_APPLE 554
+#define QI_draw_element_array_APPLE 555
+#define QI_draw_range_element_array_APPLE 556
+#define QI_weightbv_ARB 557
+#define QI_weightsv_ARB 558
+#define QI_weightiv_ARB 559
+#define QI_weightfv_ARB 560
+#define QI_weightdv_ARB 561
+#define QI_weightubv_ARB 562
+#define QI_weightusv_ARB 563
+#define QI_weightuiv_ARB 564
+#define QI_weight_pointer_ARB 565
+#define QI_vertex_blend_ARB 566
+#define QI_multi_draw_arrays 567
+#define QI_multi_draw_elements 568
+#define QI_window_pos2d 569
+#define QI_window_pos2dv 570
+#define QI_window_pos2f 571
+#define QI_window_pos2fv 572
+#define QI_window_pos2i 573
+#define QI_window_pos2iv 574
+#define QI_window_pos2s 575
+#define QI_window_pos2sv 576
+#define QI_window_pos3d 577
+#define QI_window_pos3dv 578
+#define QI_window_pos3f 579
+#define QI_window_pos3fv 580
+#define QI_window_pos3i 581
+#define QI_window_pos3iv 582
+#define QI_window_pos3s 583
+#define QI_window_pos3sv 584
+#define QI_active_stencil_face_EXT 585
+#define QI_stencil_op_separate_ATI 586
+#define QI_stencil_func_separate_ATI 587
+#define QI_flush_render_APPLE 588
+#define QI_finish_render_APPLE 589
+#define QI_swap_APPLE 590
+#define QI_delete_object_ARB 591
+#define QI_get_handle_ARB 592
+#define QI_detach_object_ARB 593
+#define QI_create_shader_object_ARB 594
+#define QI_shader_source_ARB 595
+#define QI_compile_shader_ARB 596
+#define QI_create_program_object_ARB 597
+#define QI_attach_object_ARB 598
+#define QI_link_program_ARB 599
+#define QI_use_program_object_ARB 600
+#define QI_validate_program_ARB 601
+#define QI_uniform1f_ARB 602
+#define QI_uniform2f_ARB 603
+#define QI_uniform3f_ARB 604
+#define QI_uniform4f_ARB 605
+#define QI_uniform1i_ARB 606
+#define QI_uniform2i_ARB 607
+#define QI_uniform3i_ARB 608
+#define QI_uniform4i_ARB 609
+#define QI_uniform1fv_ARB 610
+#define QI_uniform2fv_ARB 611
+#define QI_uniform3fv_ARB 612
+#define QI_uniform4fv_ARB 613
+#define QI_uniform1iv_ARB 614
+#define QI_uniform2iv_ARB 615
+#define QI_uniform3iv_ARB 616
+#define QI_uniform4iv_ARB 617
+#define QI_uniform_matrix2fv_ARB 618
+#define QI_uniform_matrix3fv_ARB 619
+#define QI_uniform_matrix4fv_ARB 620
+#define QI_get_object_parameterfv_ARB 621
+#define QI_get_object_parameteriv_ARB 622
+#define QI_get_info_log_ARB 623
+#define QI_get_attached_objects_ARB 624
+#define QI_get_uniform_location_ARB 625
+#define QI_get_active_uniform_ARB 626
+#define QI_get_uniformfv_ARB 627
+#define QI_get_uniformiv_ARB 628
+#define QI_get_shader_source_ARB 629
+#define QI_bind_attrib_location_ARB 630
+#define QI_get_active_attrib_ARB 631
+#define QI_get_attrib_location_ARB 632
+#define QI_pad 633
+#define QI_gen_queries 634
+#define QI_delete_queries 635
+#define QI_is_query 636
+#define QI_begin_query 637
+#define QI_end_query 638
+#define QI_get_queryiv 639
+#define QI_get_query_objectiv 640
+#define QI_get_query_objectuiv 641
+#define QI_bind_buffer 642
+#define QI_delete_buffers 643
+#define QI_gen_buffers 644
+#define QI_is_buffer 645
+#define QI_buffer_data 646
+#define QI_buffer_sub_data 647
+#define QI_get_buffer_sub_data 648
+#define QI_map_buffer 649
+#define QI_unmap_buffer 650
+#define QI_get_buffer_parameteriv 651
+#define QI_get_buffer_pointerv 652
+#define QI_depth_bounds_EXT 653
+#define QI_draw_buffers_ARB 654
+#define QI_is_shader 655
+#define QI_is_program 656
+#define QI_get_shaderiv 657
+#define QI_get_programiv 658
+#define QI_get_shader_info_log 659
+#define QI_get_program_info_log 660
+#define QI_stencil_func_separate 661
+#define QI_stencil_mask_separate 662
+#define QI_multi_draw_element_array_APPLE 663
+#define QI_multi_draw_range_element_array_APPLE 664
+#define QI_is_renderbuffer_EXT 665
+#define QI_bind_renderbuffer_EXT 666
+#define QI_delete_renderbuffers_EXT 667
+#define QI_gen_renderbuffers_EXT 668
+#define QI_renderbuffer_storage_EXT 669
+#define QI_get_renderbuffer_parameteriv_EXT 670
+#define QI_is_framebuffer_EXT 671
+#define QI_bind_framebuffer_EXT 672
+#define QI_delete_framebuffers_EXT 673
+#define QI_gen_framebuffers_EXT 674
+#define QI_check_framebuffer_status_EXT 675
+#define QI_framebuffer_texture1D_EXT 676
+#define QI_framebuffer_texture2D_EXT 677
+#define QI_framebuffer_texture3D_EXT 678
+#define QI_framebuffer_renderbuffer_EXT 679
+#define QI_get_framebuffer_attachment_parameteriv_EXT 680
+#define QI_generate_mipmap_EXT 681
+#define QI_buffer_parameteri_APPLE 682
+#define QI_flush_mapped_buffer_range_APPLE 683
+#define QI_program_env_parameters4fv_EXT 684
+#define QI_program_local_parameters4fv_EXT 685
+#endif
diff --git a/hw/display/qegl.c b/hw/display/qegl.c
new file mode 100644
index 0000000..1f6cfdc
--- /dev/null
+++ b/hw/display/qegl.c
@@ -0,0 +1,3565 @@
+/*
+ * qegl - paravirtual OpenGL pass-through device (PCI 1234:5145) for the Mac OS X 10.4
+ * guest's Quartz Extreme driver (G:\Tiger\qe). Windows hosts only: the device renders with
+ * WGL on its own thread and copies finished frames into guest memory (the IOFramebuffer's
+ * VRAM), so it works with any QEMU display backend.
+ *
+ * Guest protocol: G:\Tiger\qe\src\include\qegl_proto.h
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "qemu/thread.h"
+#include "qemu/rcu.h"
+#include "qemu/error-report.h"
+#include "hw/pci/pci_device.h"
+#include "hw/core/qdev-properties.h"
+#include "system/address-spaces.h"
+#include "system/memory.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+#include "qemu/main-loop.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+#include "ui/d3d-scanout.h"
+#include "qegl.h"
+#include "qegl-index.h"
+#include <tlhelp32.h>
+#include <psapi.h>
+
+#define TYPE_QEGL "qegl"
+OBJECT_DECLARE_SIMPLE_TYPE(QEGLState, QEGL)
+
+int qegl_debug;
+
+struct QEGLState {
+    PCIDevice pci;
+    MemoryRegion mmio;
+    MemoryRegion shm_mr;
+    uint8_t *shm;
+    uint32_t shm_size;
+    uint32_t shm_mb;        /* property */
+    uint32_t debug;         /* property */
+    uint32_t display_update;    /* property: 1 = push presented frames to the display at once,
+                                   0 = leave it to the GUI refresh timer, 2 = diagnostic no-op */
+    char *backend;              /* property: native (default) | zink | d3d12 | llvmpipe | softpipe */
+    char *mesa_dir;             /* property: directory of the Mesa opengl32.dll (Mesa backends);
+                                   default <directory of the QEMU executable>\mesa */
+    char *renderer;             /* property: GL_RENDERER reported to the guest ('_' = space) */
+    uint32_t heapcheck;         /* property: validate the process heaps after commands */
+    uint32_t prof;              /* property: per-command host time profile every N s */
+    int32_t pbo;                /* property: readback through a pixel-pack buffer: -1 auto
+                                   (native: yes; Mesa: no, a plain glReadPixels is faster) */
+    bool mesa;                  /* a Mesa backend is in use */
+    bool rb_pbo;
+
+    QemuThread thread;
+    QemuMutex lock;         /* protects the doorbell queue and reset requests */
+    QemuCond cond;
+    uint8_t queue[QEGL_MAX_RINGS];  /* rings in doorbell order */
+    int qhead, qcount;
+    uint64_t queued;
+    uint64_t reset_rings;
+    bool reset_all;
+    bool quit;
+    uint32_t status;
+
+    /* render thread only */
+    HWND hwnd;
+    HDC hdc;
+    HGLRC master;
+    HGLRC current;
+    bool current_in_begin;      /* diagnostics: the current context is inside glBegin/glEnd */
+    uint8_t *staging;
+    size_t staging_size;
+    QEGLGroup groups[QEGL_MAX_GROUPS];
+    QEGLContext ctx[QEGL_MAX_CTX];
+    QEGLSurface surf[QEGL_MAX_SURF];
+    struct QEGLRingState rings[QEGL_MAX_RINGS];
+    uint64_t frames;
+    uint32_t internal_name;     /* names >= 0x40000000 are ours (guest allocates from 1) */
+    int64_t st_read_us, st_copy_us, st_ring_us, st_last_us;
+    uint64_t st_frames, st_pixels;
+    int64_t st_disp_us;
+    int st_disp_n;
+    int64_t lat_frame_ns;       /* copy path: guest present time of the frame now in VRAM */
+    int64_t st_lat_us, st_lat_max_us;   /* guest present -> display update done (copy path) */
+    int st_lat_n;
+    QEMUBH *update_bh;
+    QEMUTimer *update_timer;
+    int64_t last_update_ns;
+    int update_scheduled;
+
+    /* hardware cursor: registers are written by vCPUs (lockless MMIO), applied to the console
+       by a main-loop bottom half */
+    uint32_t cursor;            /* property: 1 = offer a hardware cursor */
+    char *replay;               /* property: replay a recorded command stream, then exit */
+    uint32_t replay_loops;      /* property: how often */
+    QemuMutex cursor_lock;
+    QEMUBH *cursor_bh;
+    uint32_t cursor_size, cursor_hot;   /* register values */
+    QEMUCursor *cursor_new;     /* defined, not yet handed to the console */
+    int cursor_x, cursor_y;     /* image top-left */
+    int cursor_hx, cursor_hy;   /* hot spot of the current image */
+    bool cursor_visible;
+    bool cursor_dirty;
+    uint64_t st_cursor_defs, st_cursor_moves;
+    /* display timing (main loop) */
+    uint32_t vbl_max;           /* property: highest guest VBL rate, Hz */
+    uint32_t apps;              /* property: offer application GL / GPU Core Image (guest
+                                   default, QEGL_FEAT_APP_GL) */
+    uint32_t gl3;               /* property: offer the GL 3.x/4.x path (QEGL_FEAT_GL3, WebGL 2)
+                                   when the host GL has it */
+    uint32_t vbl_hz;            /* property: force this VBL rate (0 = follow the host monitor) */
+    uint32_t host_refresh_mhz, vbl_mhz;
+    QEMUTimer *refresh_timer;
+    /* frame pacing while animating: intervals between presents that are < 100 ms apart */
+    int64_t st_prev_present_us, st_anim_us;
+    int st_anim_n, st_anim_slow;
+    int st_anim_hist[4];        /* intervals of < 0.5, ~1, ~2, >= 2.5 guest VBL periods */
+
+    /* zero-copy scanout to a D3D11 display (ui/d3d-scanout.h); render thread unless noted */
+    uint32_t scanout;           /* property: 0 = always read frames back into VRAM */
+    QemuD3DScanout *so_avail;   /* main loop -> render thread: the display's sink, or NULL */
+    QemuConsole *so_con;        /* main loop: console whose surface sync we registered */
+    QemuD3DScanout *so_sc;      /* sink the ring belongs to */
+    QemuD3DScanoutRing *so_ring;
+    QEGLGroup *so_group;        /* share group the ring is imported into */
+    GLuint so_mem[QEMU_D3D_SCANOUT_BUFFERS], so_tex[QEMU_D3D_SCANOUT_BUFFERS];
+    GLsync so_bsync[QEMU_D3D_SCANOUT_BUFFERS];  /* end of the last blit into each buffer */
+    GLuint so_rd_fbo;           /* in so_group's root context, so_frame attached */
+    GLuint so_frame;            /* the whole picture, accumulated from the presents (the
+                                   guest's surface only covers what it redrew), top-down */
+    GLuint so_sem;              /* semaphore on the ring's D3D11 fence (native), else 0 */
+    uint64_t so_value;
+    uint8_t so_luid[8];
+    bool so_checked, so_ok;     /* GL interop extensions checked / usable */
+    bool so_failed;             /* interop failed: don't try again */
+    bool so_active;             /* frames go to the ring; VRAM may lag behind */
+    bool so_active_pub;         /* main loop's copy of so_active */
+    int so_last;                /* buffer with the newest frame, -1 */
+    int so_need[QEMU_D3D_SCANOUT_BUFFERS][4];   /* area each buffer lacks (x0,y0,x1,y1) */
+    bool so_full[QEMU_D3D_SCANOUT_BUFFERS];     /* buffer has never been written */
+    int so_stale[4];            /* framebuffer area VRAM lacks */
+    uint64_t so_gpa;            /* framebuffer of the ring */
+    uint32_t so_pitch, so_fbw, so_fbh;
+    uint8_t *so_shadow;         /* what we last wrote to VRAM (guest writes show as changes) */
+    size_t so_shadow_size;
+    int64_t so_last_present_us, so_last_sync_us, so_holdoff_until_us, so_retry_us;
+    int64_t so_mode_us;         /* ring set up for a new mode (0: the guest left that mode) */
+    struct { GLsync sync; int buf; int rect[4]; int64_t frame_ns; }
+        so_pend[QEMU_D3D_SCANOUT_BUFFERS];
+    int so_npend;               /* Mesa: published once their fence has signalled */
+    /* screendump: the main loop asks the render thread to bring VRAM up to date */
+    bool so_sync_req;           /* s->lock */
+    uint32_t so_sync_gen, so_sync_done;   /* s->lock */
+    QemuCond so_sync_cond;
+    bool so_use_sem;            /* GPU-side hand-over (native) instead of CPU fences (Mesa) */
+    int64_t so_last_check_us;
+    uint64_t st_so_frames, st_so_syncs;
+    int64_t st_so_blit_us, st_so_sync_us;
+};
+
+/* First name of qegl's own framebuffer objects; each context gets two of its own
+   (QEGLContext.surf_fbo / rd_fbo). Created by binding (EXT semantics), far above the names
+   guests use. */
+#define QEGL_INTERNAL_FBO 0x40000000u
+static GLuint qegl_fbo_name_next = QEGL_INTERNAL_FBO;
+
+/* Crash diagnostics: the command being executed, printed if the process faults (typically inside
+   the host GL driver because of a bad guest command) */
+static volatile int qegl_dbg_ring = -1;
+static QEGLState *volatile qegl_dbg_state;
+static uint64_t replay_ncmd;            /* commands replayed so far (replay mode) */
+volatile uint32_t qegl_dbg_op;
+static volatile uint32_t qegl_dbg_len;
+static const uint8_t *volatile qegl_dbg_args;
+const char *qegl_gl_name(uint32_t index);
+
+/* host GL debug messages (errors and high severity) with the guest command that caused them */
+static void APIENTRY qegl_gl_debug(GLenum source, GLenum type, GLuint id, GLenum severity,
+                                   GLsizei length, const GLchar *msg, const void *user)
+{
+    static int logged;
+    uint32_t op = qegl_dbg_op;
+    if (type != GL_DEBUG_TYPE_ERROR && severity != GL_DEBUG_SEVERITY_HIGH) {
+        return;
+    }
+    if (logged++ < 300) {
+        fprintf(stderr, "qegl: GL debug (ring %d op 0x%x %s%s): %s\n", qegl_dbg_ring, op,
+                op >= QEGL_OP_GL_BASE ? qegl_gl_name(op - QEGL_OP_GL_BASE) : "control",
+                qegl_dbg_state && qegl_dbg_state->current_in_begin ? ", in begin" : "", msg);
+    }
+}
+
+static void qegl_enable_gl_debug(void)
+{
+    if (qegl_debug >= 2 && qegl_has_gl_extension("GL_KHR_debug")) {
+        glEnable(GL_DEBUG_OUTPUT);
+        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+        glDebugMessageCallback(qegl_gl_debug, NULL);
+    }
+}
+
+/* qegl's routine log lines (debug=1 statistics, object creation, zero-copy state, ring errors,
+   cursor) go to stderr from a logger thread. A write to QEMU's redirected stderr took 10-40 ms
+   per line here (session 2): with debug=1 the per-second statistics stalled the render thread
+   ~60 ms every second (the ~65 ms present outlier of every D3D11 statistics period), each
+   surface/context creation line cost the render thread as much, and the cursor lines stalled
+   vCPUs. Fault reports, profiles and replay output still write directly. */
+static GAsyncQueue *qlog_q;
+static QemuThread qlog_thread;
+
+static void *qlog_main(void *opaque)
+{
+    for (;;) {
+        char *m = g_async_queue_pop(qlog_q);
+        fputs(m, stderr);
+        g_free(m);
+    }
+    return NULL;
+}
+
+static void G_GNUC_PRINTF(1, 2) qlog(const char *fmt, ...)
+{
+    va_list ap;
+    char *m;
+    va_start(ap, fmt);
+    m = g_strdup_vprintf(fmt, ap);
+    va_end(ap);
+    if (!qlog_q) {
+        fputs(m, stderr);
+        g_free(m);
+        return;
+    }
+    g_async_queue_push(qlog_q, m);
+}
+
+static DWORD qegl_render_tid;
+static int qegl_render_prio = THREAD_PRIORITY_HIGHEST;
+
+/* The OpenGL driver may run GL on threads of its own (NVIDIA "threaded optimization"): give
+   every thread that started in the driver's DLL the render thread's priority too. Cheap
+   enough to repeat after context creation (the driver starts threads lazily). */
+static void raise_driver_threads(void)
+{
+    static LONG (WINAPI *query)(HANDLE, int, PVOID, ULONG, PULONG);
+    HMODULE mods[2] = { GetModuleHandleA("nvoglv64.dll"), NULL };
+    MODULEINFO mi[2] = { 0 };
+    THREADENTRY32 te = { .dwSize = sizeof(te) };
+    DWORD pid = GetCurrentProcessId();
+    HANDLE snap;
+    int m;
+    if (!query) {
+        query = (void *)GetProcAddress(GetModuleHandleA("ntdll.dll"),
+                                       "NtQueryInformationThread");
+        if (!query) {
+            return;
+        }
+    }
+    for (m = 0; m < 2; m++) {
+        if (mods[m]) {
+            K32GetModuleInformation(GetCurrentProcess(), mods[m], &mi[m], sizeof(mi[m]));
+        }
+    }
+    if (!mi[0].lpBaseOfDll && !mi[1].lpBaseOfDll) {
+        return;
+    }
+    snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+    if (snap == INVALID_HANDLE_VALUE) {
+        return;
+    }
+    if (Thread32First(snap, &te)) {
+        do {
+            HANDLE th;
+            PVOID start = NULL;
+            if (te.th32OwnerProcessID != pid || te.th32ThreadID == qegl_render_tid) {
+                continue;
+            }
+            th = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_INFORMATION, FALSE,
+                            te.th32ThreadID);
+            if (!th) {
+                continue;
+            }
+            /* 9 = ThreadQuerySetWin32StartAddress */
+            if (query(th, 9, &start, sizeof(start), NULL) == 0) {
+                for (m = 0; m < 2; m++) {
+                    uint8_t *b = mi[m].lpBaseOfDll;
+                    if (b && (uint8_t *)start >= b && (uint8_t *)start < b + mi[m].SizeOfImage &&
+                        GetThreadPriority(th) != qegl_render_prio) {
+                        SetThreadPriority(th, qegl_render_prio);
+                        if (qegl_debug) {
+                            qlog("qegl: GL driver thread %lu -> priority %d\n",
+                                    (unsigned long)te.th32ThreadID, qegl_render_prio);
+                        }
+                    }
+                }
+            }
+            CloseHandle(th);
+        } while (Thread32Next(snap, &te));
+    }
+    CloseHandle(snap);
+}
+
+static LONG CALLBACK qegl_fault_handler(EXCEPTION_POINTERS *ep)
+{
+    DWORD code = ep->ExceptionRecord->ExceptionCode;
+    static volatile LONG in_fault;
+    if (InterlockedExchange(&in_fault, 1)) {
+        return EXCEPTION_CONTINUE_SEARCH;
+    }
+    if (code == EXCEPTION_ACCESS_VIOLATION || code == 0xc0000374 /* heap corruption */ ||
+        code == EXCEPTION_ILLEGAL_INSTRUCTION || code == EXCEPTION_STACK_OVERFLOW ||
+        code == 0xC0000409 /* fail fast */) {
+        uint32_t op = qegl_dbg_op;
+        const uint32_t *a = GetCurrentThreadId() == qegl_render_tid ?
+                            (const uint32_t *)qegl_dbg_args : NULL;
+        {
+            /* faulting module and call stack (unwound from the exception context), as
+               module+offset for llvm-symbolizer */
+            CONTEXT ctx = *ep->ContextRecord;
+            int depth;
+            for (depth = 0; depth < 24 && ctx.Rip; depth++) {
+                DWORD64 base = 0;
+                PVOID hd = NULL;
+                DWORD64 est = 0;
+                PRUNTIME_FUNCTION fe = RtlLookupFunctionEntry(ctx.Rip, &base, NULL);
+                HMODULE m = NULL;
+                char mn[MAX_PATH] = "?";
+                const char *sl;
+                if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+                                       GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+                                       (LPCSTR)ctx.Rip, &m)) {
+                    GetModuleFileNameA(m, mn, sizeof(mn));
+                }
+                sl = strrchr(mn, '\\');
+                fprintf(stderr, "qegl:   #%d %s+0x%llx\n", depth, sl ? sl + 1 : mn,
+                        (unsigned long long)(ctx.Rip - (DWORD64)(uintptr_t)m));
+                if (!fe) {
+                    /* leaf function: the return address is at the stack pointer */
+                    ctx.Rip = *(DWORD64 *)(uintptr_t)ctx.Rsp;
+                    ctx.Rsp += 8;
+                    continue;
+                }
+                RtlVirtualUnwind(UNW_FLAG_NHANDLER, base, ctx.Rip, fe, &ctx, &hd, &est, NULL);
+            }
+        }
+        fprintf(stderr, "qegl: FAULT 0x%lx at %p (%s %p) thread %lu while ring %d executed op "
+                "0x%x (%s) len %u",
+                (unsigned long)code, ep->ExceptionRecord->ExceptionAddress,
+                ep->ExceptionRecord->ExceptionInformation[0] ? "writing" : "reading",
+                (void *)ep->ExceptionRecord->ExceptionInformation[1],
+                (unsigned long)GetCurrentThreadId(), qegl_dbg_ring, op,
+                op >= QEGL_OP_GL_BASE ? qegl_gl_name(op - QEGL_OP_GL_BASE) : "control",
+                qegl_dbg_len);
+        if (a && qegl_dbg_len >= 8) {
+            unsigned i;
+            fprintf(stderr, " args");
+            for (i = 0; i < MIN(qegl_dbg_len - 8, 64) / 4; i++) {
+                fprintf(stderr, " %x", a[i]);
+            }
+        }
+        if (qegl_dbg_state) {
+            QEGLState *s = qegl_dbg_state;
+            int i, nc = 0, ng = 0, ns = 0;
+            for (i = 0; i < QEGL_MAX_CTX; i++) {
+                nc += s->ctx[i].id != 0;
+            }
+            for (i = 0; i < QEGL_MAX_GROUPS; i++) {
+                ng += s->groups[i].id != 0;
+            }
+            for (i = 0; i < QEGL_MAX_SURF; i++) {
+                ns += s->surf[i].id != 0;
+            }
+            fprintf(stderr, " | live: %d contexts, %d groups, %d surfaces, current %p", nc, ng,
+                    ns, (void *)s->current);
+            if (s->replay) {
+                fprintf(stderr, " | replay command #%" PRIu64, replay_ncmd);
+            }
+        }
+        fprintf(stderr, " | render thread %lu\n", (unsigned long)qegl_render_tid);
+        fflush(stderr);
+    }
+    in_fault = 0;
+    return EXCEPTION_CONTINUE_SEARCH;
+}
+
+/* Host time per command type (QEGL_PROF=1 in QEMU's environment): every 5 s the commands
+   that took the most render-thread time, with counts and averages. */
+#define QEGL_PROF_SLOTS (0x100 + 1024)
+static int qegl_prof = -1, qegl_fbocheck = -1;
+static uint64_t prof_ticks[QEGL_PROF_SLOTS], prof_count[QEGL_PROF_SLOTS];
+static int64_t prof_last;
+
+static inline int64_t prof_now(void)
+{
+    LARGE_INTEGER t;
+    QueryPerformanceCounter(&t);
+    return t.QuadPart;
+}
+
+static void prof_add(uint32_t op, int64_t t0)
+{
+    uint32_t slot = op >= QEGL_OP_GL_BASE ? 0x100 + (op - QEGL_OP_GL_BASE) : op;
+    if (slot < QEGL_PROF_SLOTS) {
+        prof_ticks[slot] += prof_now() - t0;
+        prof_count[slot]++;
+    }
+}
+
+static void prof_report(void)
+{
+    LARGE_INTEGER f;
+    int64_t now = g_get_monotonic_time();
+    uint64_t total = 0;
+    int i, k;
+    if (now - prof_last < 5000000) {
+        return;
+    }
+    prof_last = now;
+    QueryPerformanceFrequency(&f);
+    for (i = 0; i < QEGL_PROF_SLOTS; i++) {
+        total += prof_ticks[i];
+    }
+    if (!total) {
+        return;
+    }
+    fprintf(stderr, "qegl: prof (5 s): %.1f ms in commands; top:", total * 1e3 / f.QuadPart);
+    for (k = 0; k < 14; k++) {
+        int best = -1;
+        for (i = 0; i < QEGL_PROF_SLOTS; i++) {
+            if (prof_ticks[i] && (best < 0 || prof_ticks[i] > prof_ticks[best])) {
+                best = i;
+            }
+        }
+        if (best < 0) {
+            break;
+        }
+        char nm[48];
+        if (best >= 0x100) {
+            snprintf(nm, sizeof(nm), "%s", qegl_gl_name(best - 0x100));
+        } else if (best == 0x80) {
+            snprintf(nm, sizeof(nm), "(surface storage)");
+        } else if (best == 0x81) {
+            snprintf(nm, sizeof(nm), "(wglMakeCurrent)");
+        } else {
+            snprintf(nm, sizeof(nm), "ctl:%x", best);
+        }
+        fprintf(stderr, " %s %.1fms/%llu=%.1fus", nm,
+                prof_ticks[best] * 1e3 / f.QuadPart, (unsigned long long)prof_count[best],
+                prof_ticks[best] * 1e6 / f.QuadPart / prof_count[best]);
+        prof_ticks[best] = 0;
+        prof_count[best] = 0;
+    }
+    fprintf(stderr, "\n");
+    memset(prof_ticks, 0, sizeof(prof_ticks));
+    memset(prof_count, 0, sizeof(prof_count));
+}
+
+static void so_release(QEGLState *s);
+
+static GLuint internal_name(QEGLState *s)
+{
+    if (s->internal_name < 0x40000001u) {
+        s->internal_name = 0x40000001u;
+    }
+    return s->internal_name++;
+}
+
+GLuint qegl_internal_name(QEGLState *s)
+{
+    return internal_name(s);
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* contexts, groups, surfaces */
+
+static int64_t prof_mc_us;        /* prof: time in context switches */
+static uint32_t prof_mc_n;
+
+static void make_current(QEGLState *s, HGLRC rc)
+{
+    if (s->current != rc) {
+        int64_t t_mc = g_get_monotonic_time();
+        if (s->current && !s->current_in_begin) {
+            glFlush();  /* make this context's work visible to the next one */
+        }
+        int64_t mt0 = qegl_prof > 0 ? prof_now() : 0;
+        bool mok = wglMakeCurrent(s->hdc, rc);
+        if (qegl_prof > 0) {
+            prof_add(0x81, mt0);
+        }
+        if (!mok) {
+            static int logged;
+            if (logged++ < 20) {
+                error_report("qegl: wglMakeCurrent(%p) failed (%lu)", (void *)rc,
+                             GetLastError());
+            }
+            wglMakeCurrent(s->hdc, s->master);
+            s->current = s->master;
+            return;
+        }
+        s->current = rc;
+        prof_mc_us += g_get_monotonic_time() - t_mc;
+        prof_mc_n++;
+    }
+}
+
+static QEGLContext *find_ctx(QEGLState *s, uint32_t id)
+{
+    int i;
+    if (!id) {
+        return NULL;
+    }
+    for (i = 0; i < QEGL_MAX_CTX; i++) {
+        if (s->ctx[i].id == id) {
+            return &s->ctx[i];
+        }
+    }
+    return NULL;
+}
+
+QEGLSurface *qegl_find_surface(QEGLState *s, uint32_t id)
+{
+    int i;
+    if (!id) {
+        return NULL;
+    }
+    for (i = 0; i < QEGL_MAX_SURF; i++) {
+        if (s->surf[i].id == id) {
+            return &s->surf[i];
+        }
+    }
+    return NULL;
+}
+
+/* A WGL context, sharing objects with `share` from its creation (WGL_ARB_create_context)
+   instead of wglShareLists after the fact, which the NVIDIA driver has been seen crashing in
+   after many contexts came and went (Core Image creates and destroys them all the time). */
+static HGLRC create_rc(QEGLState *s, HGLRC share)
+{
+    HGLRC rc;
+    /* Mesa: WGL_ARB_create_context is always there (resolved into qegl_glfn[]) */
+    if (s->mesa || epoxy_has_wgl_extension(s->hdc, "WGL_ARB_create_context")) {
+        const int attrs[] = {
+            WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, 0
+        };
+        rc = wglCreateContextAttribsARB(s->hdc, share, attrs);
+        if (rc) {
+            return rc;
+        }
+    }
+    rc = wglCreateContext(s->hdc);
+    if (rc && share && !wglShareLists(share, rc)) {
+        wglDeleteContext(rc);
+        return NULL;
+    }
+    return rc;
+}
+
+static QEGLGroup *group_get(QEGLState *s, uint32_t id)
+{
+    QEGLGroup *g = NULL;
+    int i;
+    for (i = 0; i < QEGL_MAX_GROUPS; i++) {
+        if (s->groups[i].id == id && id) {
+            return &s->groups[i];
+        }
+        if (!g && !s->groups[i].id) {
+            g = &s->groups[i];
+        }
+    }
+    if (!g) {
+        return NULL;
+    }
+    memset(g, 0, sizeof(*g));
+    g->rc = create_rc(s, NULL);
+    if (!g->rc) {
+        return NULL;
+    }
+    g->id = id ? id : 0xffffffffu;
+    return g;
+}
+
+static void group_unref(QEGLState *s, QEGLGroup *g)
+{
+    if (!g || --g->refs > 0) {
+        return;
+    }
+    if (s->so_group == g) {
+        so_release(s);      /* zero-copy ring: VRAM gets the last frame first */
+    }
+    make_current(s, g->rc);
+    qegl_group_fbos_release(g);
+    qegl_gl3_group_free(g);
+    if (g->read_fbo) {
+        glBindFramebuffer(GL_FRAMEBUFFER, g->read_fbo);
+        qegl_fbo_detach_all(GL_FRAMEBUFFER);
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glDeleteFramebuffers(1, &g->read_fbo);
+    }
+    if (g->pbo) {
+        glDeleteBuffers(1, &g->pbo);
+    }
+    make_current(s, s->master);
+    wglDeleteContext(g->rc);
+    memset(g, 0, sizeof(*g));
+}
+
+/* switch to the group's root context and make sure its helper objects exist */
+static void group_enter(QEGLState *s, QEGLGroup *g)
+{
+    make_current(s, g->rc);
+    if (!g->read_fbo) {
+        glGenFramebuffers(1, &g->read_fbo);
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    }
+}
+
+static GLenum map_buffer(QEGLSurface *sf, GLenum b)
+{
+    bool dbl = sf && (sf->flags & QEGL_SURF_DOUBLE);
+    switch (b) {
+    case GL_FRONT:
+    case GL_FRONT_LEFT:
+    case GL_FRONT_RIGHT:
+    case GL_LEFT:
+    case GL_RIGHT:
+        return dbl ? GL_COLOR_ATTACHMENT1 : GL_COLOR_ATTACHMENT0;
+    case GL_BACK:
+    case GL_BACK_LEFT:
+    case GL_BACK_RIGHT:
+    case GL_FRONT_AND_BACK:
+        return GL_COLOR_ATTACHMENT0;
+    }
+    return GL_NONE;
+}
+
+/* apply the guest's glDrawBuffer/glReadBuffer while its surface (fbo 0) is bound */
+void qegl_apply_draw_buffer(QEGLExec *e)
+{
+    QEGLContext *c = e->ctx;
+    QEGLSurface *sf;
+    if (!c || c->guest_fbo) {
+        return;
+    }
+    sf = qegl_find_surface(e->s, c->draw_surf);
+    if (!sf) {
+        return;
+    }
+    if (c->draw_buffer == GL_FRONT_AND_BACK && (sf->flags & QEGL_SURF_DOUBLE)) {
+        static const GLenum both[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
+        glDrawBuffers(2, both);
+    } else {
+        glDrawBuffer(map_buffer(sf, c->draw_buffer));
+    }
+    if (!c->guest_read_fbo) {   /* GL3 path: a guest read framebuffer keeps its read buffer */
+        glReadBuffer(map_buffer(sf, c->read_buffer));
+    }
+}
+
+/* Bind the current context's draw surface as GL_FRAMEBUFFER (what the guest calls fbo 0) */
+void qegl_bind_draw_surface(QEGLExec *e)
+{
+    QEGLContext *c = e->ctx;
+    QEGLSurface *sf;
+    if (!c) {
+        return;
+    }
+    sf = qegl_find_surface(e->s, c->draw_surf);
+    if (!sf || sf->group != c->group) {
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        return;
+    }
+    glBindFramebufferEXT(GL_FRAMEBUFFER, c->surf_fbo);
+    if (c->fbo_surf != sf->id || c->fbo_gen != sf->gen) {
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
+                               sf->tex[0], 0);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE,
+                               (sf->flags & QEGL_SURF_DOUBLE) ? sf->tex[1] : 0, 0);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
+                                  (sf->flags & (QEGL_SURF_DEPTH | QEGL_SURF_STENCIL)) ?
+                                  sf->depth_stencil : 0);
+        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+            error_report("qegl: surface 0x%x fbo incomplete", sf->id);
+        }
+        c->fbo_surf = sf->id;
+        c->fbo_gen = sf->gen;
+    }
+    qegl_apply_draw_buffer(e);
+}
+
+static void rebind_if_current(QEGLExec *e, QEGLSurface *sf)
+{
+    if (e->ctx && e->ctx->draw_surf == sf->id && !e->ctx->guest_fbo &&
+        e->s->current == e->ctx->hglrc) {
+        qegl_bind_draw_surface(e);
+    }
+}
+
+/* (re)allocate storage; must run in a context of the surface's group */
+static void surf_alloc_storage(QEGLSurface *sf)
+{
+    int n = (sf->flags & QEGL_SURF_DOUBLE) ? 2 : 1;
+    int i;
+    int64_t pt = qegl_prof > 0 ? prof_now() : 0;
+    GLint prev_tex = 0, prev_rb = 0;
+    /* may run inside a guest context: keep its bindings */
+    glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &prev_tex);
+    glGetIntegerv(GL_RENDERBUFFER_BINDING, &prev_rb);
+    for (i = 0; i < n; i++) {
+        glBindTexture(GL_TEXTURE_RECTANGLE, sf->tex[i]);
+        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, sf->sw, sf->sh, 0, GL_BGRA,
+                     GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
+    }
+    glBindTexture(GL_TEXTURE_RECTANGLE, prev_tex);
+    if (sf->flags & (QEGL_SURF_DEPTH | QEGL_SURF_STENCIL)) {
+        glBindRenderbufferEXT(GL_RENDERBUFFER, sf->depth_stencil);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, sf->sw, sf->sh);
+    }
+    glBindRenderbufferEXT(GL_RENDERBUFFER, prev_rb);
+    sf->gen++;
+    if (qegl_prof > 0) {
+        prof_add(0x80, pt);
+    }
+}
+
+static void surf_set_size(QEGLExec *e, QEGLSurface *sf, uint32_t w, uint32_t h)
+{
+    sf->w = w;
+    sf->h = h;
+    if (w > sf->sw || h > sf->sh) {
+        HGLRC prev = e->s->current;
+        /* grow in steps so a WindowServer surface that tracks the dirty region settles */
+        sf->sw = MAX(sf->sw, ROUND_UP(w, 256));
+        sf->sh = MAX(sf->sh, ROUND_UP(h, 256));
+        if (!e->ctx || e->ctx->group != sf->group || prev != e->ctx->hglrc) {
+            make_current(e->s, sf->group->rc);
+        }
+        surf_alloc_storage(sf);
+        make_current(e->s, prev);
+        rebind_if_current(e, sf);
+    }
+}
+
+static void surf_detach_everywhere(QEGLState *s, QEGLSurface *sf);
+
+static int surf_create(QEGLExec *e, uint32_t id, uint32_t w, uint32_t h, uint32_t flags)
+{
+    QEGLState *s = e->s;
+    QEGLSurface *sf;
+    int i;
+    if (!id || !w || !h || w > 16384 || h > 16384) {
+        return QEGL_ERR_BAD_OBJECT;
+    }
+    sf = qegl_find_surface(s, id);
+    if (sf) {
+        if (e->ctx && sf->group == e->ctx->group) {
+            sf->flags |= flags & QEGL_SURF_SHARED;
+            surf_set_size(e, sf, w, h);
+            return 0;
+        }
+        if (e->ctx && !QEGL_IS_ACCEL_SURF(id)) {
+            return QEGL_ERR_BAD_OBJECT;
+        }
+        /* an IOAccel surface id re-used by another share group: recreate there */
+        surf_detach_everywhere(s, sf);
+        make_current(s, sf->group->rc);
+        glDeleteTextures(2, sf->tex);
+        glDeleteRenderbuffers(1, &sf->depth_stencil);
+        group_unref(s, sf->group);
+        memset(sf, 0, sizeof(*sf));
+    } else {
+        for (i = 0; i < QEGL_MAX_SURF && s->surf[i].id; i++) {
+        }
+        if (i == QEGL_MAX_SURF) {
+            return QEGL_ERR_RANGE;
+        }
+        sf = &s->surf[i];
+    }
+    if (!e->ctx) {
+        return QEGL_ERR_BAD_OBJECT;
+    }
+    make_current(s, e->ctx->hglrc);
+    memset(sf, 0, sizeof(*sf));
+    sf->id = id;
+    sf->w = w;
+    sf->h = h;
+    sf->sw = w;
+    sf->sh = h;
+    sf->flags = flags;
+    sf->owner_ring = e->ring;
+    sf->group = e->ctx->group;
+    sf->group->refs++;
+    sf->tex[0] = internal_name(s);
+    sf->tex[1] = internal_name(s);
+    sf->depth_stencil = internal_name(s);
+    surf_alloc_storage(sf);
+    if (qegl_debug) {
+        qlog("qegl: ring %d surf 0x%x create %ux%u flags %x group %u\n", e->ring,
+                id, w, h, flags, sf->group->id);
+    }
+    return 0;
+}
+
+/* Detach a surface from every framebuffer object that still points at it before its textures
+   are deleted, so that no FBO keeps them alive and no FBO is ever deleted (or its context
+   destroyed) with the surface attached: see qegl_fbo_detach_all for the NVIDIA driver bug
+   that turns that into heap corruption. */
+static void surf_detach_everywhere(QEGLState *s, QEGLSurface *sf)
+{
+    QEGLGroup *g = sf->group;
+    int i;
+    for (i = 0; i < QEGL_MAX_CTX; i++) {
+        QEGLContext *c = &s->ctx[i];
+        GLint prev_draw = 0, prev_read = 0;
+        if (!c->id || c->group != g || (c->fbo_surf != sf->id && c->rd_surf != sf->id)) {
+            continue;
+        }
+        make_current(s, c->hglrc);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw);
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read);
+        if (c->surf_fbo && c->fbo_surf == sf->id) {
+            glBindFramebufferEXT(GL_FRAMEBUFFER, c->surf_fbo);
+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
+                                   0, 0);
+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE,
+                                   0, 0);
+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
+                                      GL_RENDERBUFFER, 0);
+            c->fbo_surf = 0;
+        }
+        if (c->rd_surf == sf->id) {
+            glBindFramebufferEXT(GL_READ_FRAMEBUFFER, c->rd_fbo);
+            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                   GL_TEXTURE_RECTANGLE, 0, 0);
+            c->rd_surf = 0;
+        }
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, prev_draw);
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, prev_read);
+    }
+    if (g->read_fbo && g->rd_surf == sf->id) {
+        make_current(s, g->rc);
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, g->read_fbo);
+        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
+                               0, 0);
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+        g->rd_surf = 0;
+    }
+}
+
+static void surf_free(QEGLState *s, QEGLSurface *sf)
+{
+    surf_detach_everywhere(s, sf);
+    make_current(s, sf->group->rc);
+    glDeleteTextures(2, sf->tex);
+    glDeleteRenderbuffers(1, &sf->depth_stencil);
+    group_unref(s, sf->group);
+    memset(sf, 0, sizeof(*sf));
+}
+
+/* A context's own framebuffer objects are detached from their textures and renderbuffers
+   before they are deleted and the context destroyed (see qegl_fbo_detach_all). The guest's
+   framebuffer objects belong to the share group and go with it (group_unref). */
+static void ctx_free(QEGLState *s, QEGLContext *c)
+{
+    GLuint fbos[3] = { c->surf_fbo, c->rd_fbo, c->so_fbo };
+    make_current(s, c->hglrc);
+    glBindFramebufferEXT(GL_FRAMEBUFFER, c->surf_fbo);
+    qegl_fbo_detach_all(GL_FRAMEBUFFER);
+    glBindFramebufferEXT(GL_FRAMEBUFFER, c->rd_fbo);
+    qegl_fbo_detach_all(GL_FRAMEBUFFER);
+    if (c->so_fbo) {
+        glBindFramebufferEXT(GL_FRAMEBUFFER, c->so_fbo);
+        qegl_fbo_detach_all(GL_FRAMEBUFFER);
+    }
+    glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
+    glDeleteFramebuffers(c->so_fbo ? 3 : 2, fbos);
+    glFinish();
+    qegl_gl3_ctx_free(c);       /* its vertex array / transform feedback objects go with it */
+    make_current(s, s->master);
+    wglDeleteContext(c->hglrc);
+    group_unref(s, c->group);
+    memset(c, 0, sizeof(*c));
+}
+
+static void reset_ring_objects(QEGLState *s, int ring)
+{
+    int i;
+    /* contexts (with their framebuffer objects) first, then the textures they pointed at */
+    for (i = 0; i < QEGL_MAX_CTX; i++) {
+        if (s->ctx[i].id && (ring < 0 || s->ctx[i].owner_ring == ring)) {
+            ctx_free(s, &s->ctx[i]);
+        }
+    }
+    for (i = 0; i < QEGL_MAX_SURF; i++) {
+        QEGLSurface *sf = &s->surf[i];
+        if (!sf->id || (ring >= 0 && sf->owner_ring != ring)) {
+            continue;
+        }
+        if (ring >= 0 && (sf->flags & QEGL_SURF_SHARED)) {
+            /* a pbuffer: another context of the group may still draw to it */
+            int k, heir = -1;
+            for (k = 0; k < QEGL_MAX_CTX; k++) {
+                if (s->ctx[k].id && s->ctx[k].group == sf->group &&
+                    s->ctx[k].owner_ring != ring) {
+                    heir = s->ctx[k].owner_ring;
+                    break;
+                }
+            }
+            if (heir >= 0) {
+                sf->owner_ring = heir;
+                continue;
+            }
+        }
+        surf_free(s, sf);
+    }
+    make_current(s, s->master);
+    for (i = 0; i < QEGL_MAX_RINGS; i++) {
+        struct QEGLRingState *rs = &s->rings[i];
+        if (ring >= 0 && i != ring) {
+            continue;
+        }
+        if (rs->map && rs->owned) {
+            g_free(rs->map);
+        } else if (rs->map) {
+            address_space_unmap(&address_space_memory, rs->map, rs->map_len, true,
+                                 rs->map_len);
+        }
+        memset(rs, 0, sizeof(*rs));
+        if (ring >= 0) {
+            struct qegl_ring_desc *rd =
+                (struct qegl_ring_desc *)(s->shm + QEGL_RING_TABLE_OFF + 64 * i);
+            qatomic_store_release(&rd->reset_ack, rd->reset_ack + 1);
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* readback / present */
+
+/* Read the logical image of a surface's front (or back) buffer into the staging buffer:
+   GL bottom-up rows, w*4 bytes each. Leaves the group's root context current. */
+/* Read the logical image of a surface's front (or back) buffer through the group's pixel
+   pack buffer and map it: GL bottom-up rows, w*4 bytes each. Reads in the ring's own context
+   when it belongs to the surface's group (no context switch), else in the group's root
+   context. Call readback_done() when finished with the pointer. */
+static GLint rb_prev_fb, rb_prev_rb, rb_prev_pbo;
+static bool rb_in_ctx;
+/* origin and row length of the image surf_readback() returned (a region of the surface on the
+   plain-readback path, else the whole surface) */
+static int rb_x0, rb_y0, rb_w;
+
+/* rw/rh != 0: only the region (rx, ry, rw, rh) in GL coordinates is needed; the plain path
+   (Mesa backends) then reads just that, the PBO path (native) reads the whole surface */
+static uint8_t *surf_readback(QEGLExec *e, QEGLSurface *sf, int back, int rx, int ry, int rw,
+                              int rh)
+{
+    QEGLState *s = e->s;
+    QEGLGroup *g = sf->group;
+    size_t need = (size_t)sf->w * sf->h * 4;
+    int idx = ((sf->flags & QEGL_SURF_DOUBLE) && !back) ? 1 : 0;
+    void *p;
+
+    rb_x0 = 0;
+    rb_y0 = 0;
+    rb_w = sf->w;
+    rb_in_ctx = e->ctx && e->ctx->group == g && s->current == e->ctx->hglrc;
+    if (rb_in_ctx) {
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &rb_prev_fb);
+        glGetIntegerv(GL_READ_BUFFER, &rb_prev_rb);
+        glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &rb_prev_pbo);
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, e->ctx->rd_fbo);
+    } else {
+        group_enter(s, g);
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, g->read_fbo);
+    }
+    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
+                           sf->tex[idx], 0);
+    if (rb_in_ctx) {
+        e->ctx->rd_surf = sf->id;
+    } else {
+        g->rd_surf = sf->id;
+    }
+    glReadBuffer(GL_COLOR_ATTACHMENT0);
+    p = NULL;
+    /* Mesa (zink, d3d12): mapping a pixel-pack buffer right after glReadPixels into it waits
+       ~2.5 ms for a 1280x720 frame; a plain glReadPixels takes ~1.1 ms (G:\Tiger\qe-vk) */
+    if (s->rb_pbo) {
+        if (!g->pbo) {
+            g->pbo = internal_name(s);
+        }
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, g->pbo);
+        if (g->pbo_size < need) {
+            g->pbo_size = ROUND_UP(need, 1 << 20);
+            glBufferData(GL_PIXEL_PACK_BUFFER, g->pbo_size, NULL, GL_STREAM_READ);
+        }
+        glReadPixels(0, 0, sf->w, sf->h, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
+        p = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, need, GL_MAP_READ_BIT);
+    }
+    if (!p) {
+        /* fall back to a plain readback */
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+        if (s->staging_size < need) {
+            g_free(s->staging);
+            s->staging = g_malloc(need);
+            s->staging_size = need;
+        }
+        if (rw > 0 && rh > 0 && !s->rb_pbo) {
+            rb_x0 = rx;
+            rb_y0 = ry;
+            rb_w = rw;
+            glReadPixels(rx, ry, rw, rh, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, s->staging);
+        } else {
+            glReadPixels(0, 0, sf->w, sf->h, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, s->staging);
+        }
+        return s->staging;
+    }
+    return p;
+}
+
+static void readback_done(QEGLExec *e, QEGLSurface *sf)
+{
+    GLint bound = 0;
+    glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &bound);
+    if (bound && (GLuint)bound == sf->group->pbo) {
+        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
+    }
+    if (rb_in_ctx) {
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, rb_prev_pbo);
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, rb_prev_fb);
+        glReadBuffer(rb_prev_rb);
+    } else {
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+    }
+}
+
+/* Ask the main loop to show a new frame. Runs on the render thread. Every main-loop wakeup
+   costs the vCPUs (measured: waking it on every present cost ~13% of redraw throughput),
+   so it is woken at most once per guest VBL period: immediately if the last update is old
+   enough, otherwise by a timer at the next slot. */
+static void qegl_request_update(QEGLState *s)
+{
+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+    int64_t last = qatomic_read(&s->last_update_ns);
+    /* at most one display update per guest VBL period (minus 10% for timer jitter) */
+    uint32_t vbl = qatomic_read(&s->vbl_mhz);
+    int64_t min_ns = vbl ? 900000000000ll / vbl : 15000000;
+    if (now - last >= min_ns) {
+        if (!qatomic_xchg(&s->update_scheduled, 1)) {
+            qatomic_set(&s->last_update_ns, now);
+            qemu_bh_schedule(s->update_bh);
+        }
+    } else if (!timer_pending(s->update_timer)) {
+        timer_mod(s->update_timer, last + min_ns);
+    }
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/*
+ * Zero-copy scanout: with a display that composes with Direct3D 11 (-display d3d11), presented
+ * frames go to it as shared textures instead of being read back into VRAM
+ * (ui/d3d-scanout.h).
+ *  - The display allocates a ring of shared D3D11 textures for the framebuffer size; they are
+ *    imported here (GL_EXT_memory_object_win32) into the presenting share group.
+ *  - The guest's surface only holds what WindowServer just redrew (qegl sizes it to the dirty
+ *    region), so qegl keeps the whole picture in an accumulator texture: a present blits the
+ *    damage into it (upside down: D3D rows are top-down), then copies into a free buffer
+ *    everything that buffer lacks. NVIDIA's GL then signals the ring's D3D11 fence
+ *    (GL_EXT_semaphore_win32) and the display's GPU waits for it. Mesa zink cannot import D3D11 fences: its frames are handed over once a
+ *    GL fence shows the blit done, polled between rings, so the render thread never waits for
+ *    the GPU while there is guest work.
+ *  - VRAM (the console surface, which the guest can read) catches up lazily: 200 ms after the
+ *    last frame, at least once a second while frames keep coming, before a screendump, and
+ *    before frames go back through VRAM. Guest writes into VRAM in between (kernel panic,
+ *    console text, an application drawing into the framebuffer) show up as differences from
+ *    what was last written there: they are kept, and frames go through VRAM for 2 s. Except
+ *    right after a mode change: the guest clears the new framebuffer then, sometimes after its
+ *    first frames reached us, and that clear is not content (kept, it left a band of the clear
+ *    colour over the newly exposed area until something redrew it).
+ *  - Only while the guest uses the hardware cursor (a software cursor is drawn into VRAM) and
+ *    only if every display of the console takes frames this way (VNC needs the pixels).
+ */
+#define SO_SETTLE_US  200000
+/* QEGL_SO_VERIFY=1: also present through VRAM and compare (self-check, diagnostics) */
+static int so_verify = -1;
+#define SO_STALE_US  1000000
+#define SO_CHECK_US   500000
+#define SO_HOLDOFF_US 2000000
+#define SO_RETRY_US    100000
+#define SO_MODE_GRACE_US 3000000
+
+static void so_rect_add(int *r, int x0, int y0, int x1, int y1)
+{
+    if (x1 <= x0 || y1 <= y0) {
+        return;
+    }
+    if (r[2] <= r[0] || r[3] <= r[1]) {
+        r[0] = x0;
+        r[1] = y0;
+        r[2] = x1;
+        r[3] = y1;
+        return;
+    }
+    r[0] = MIN(r[0], x0);
+    r[1] = MIN(r[1], y0);
+    r[2] = MAX(r[2], x1);
+    r[3] = MAX(r[3], y1);
+}
+
+static bool so_rect_empty(const int *r)
+{
+    return r[2] <= r[0] || r[3] <= r[1];
+}
+
+static bool rc_in_group(QEGLState *s, HGLRC rc, QEGLGroup *g)
+{
+    int i;
+    if (!rc || !g) {
+        return false;
+    }
+    if (rc == g->rc) {
+        return true;
+    }
+    for (i = 0; i < QEGL_MAX_CTX; i++) {
+        if (s->ctx[i].id && s->ctx[i].hglrc == rc) {
+            return s->ctx[i].group == g;
+        }
+    }
+    return false;
+}
+
+/* a context is current: can this GL do it at all? */
+static bool so_check(QEGLState *s)
+{
+    if (!s->so_checked) {
+        s->so_checked = true;
+        s->so_ok = qegl_has_gl_extension("GL_EXT_memory_object") &&
+                   qegl_has_gl_extension("GL_EXT_memory_object_win32");
+        /* zink: GL_EXT_semaphore_win32 imports only opaque Vulkan semaphores, not D3D fences */
+        s->so_use_sem = s->so_ok && !s->mesa &&
+                        qegl_has_gl_extension("GL_EXT_semaphore") &&
+                        qegl_has_gl_extension("GL_EXT_semaphore_win32");
+        if (s->so_ok) {
+            glGetUnsignedBytevEXT(GL_DEVICE_LUID_EXT, s->so_luid);
+            while (glGetError() != GL_NO_ERROR) {
+            }
+        }
+        info_report("qegl: zero-copy scanout %s", !s->so_ok ? "not possible (no "
+                    "GL_EXT_memory_object_win32)" : s->so_use_sem ?
+                    "possible (shared textures, D3D11 fence)" :
+                    "possible (shared textures, CPU-side fences)");
+    }
+    return s->so_ok;
+}
+
+/* the guest wrote into VRAM itself since our last sync? */
+static bool so_foreign(QEGLState *s, const uint8_t *fb)
+{
+    uint32_t y, row = s->so_fbw * 4;
+
+    if (!s->so_shadow) {
+        return false;
+    }
+    for (y = 0; y < s->so_fbh; y++) {
+        if (memcmp(fb + (size_t)y * s->so_pitch, s->so_shadow + (size_t)y * row, row)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * Bring VRAM up to date with the newest frame (the area it lacks). Returns true if the guest
+ * has written into VRAM itself meanwhile; those pixels are kept.
+ */
+static bool so_vram_sync(QEGLState *s)
+{
+    int *r = s->so_stale;
+    HGLRC prev = s->current;
+    hwaddr len = (hwaddr)s->so_pitch * s->so_fbh;
+    int64_t t0 = g_get_monotonic_time();
+    uint32_t row = s->so_fbw * 4;
+    bool foreign;
+    uint8_t *fb;
+    int x, y, w, h;
+
+    if (!s->so_active || !s->so_ring) {
+        return false;
+    }
+    s->so_last_check_us = t0;
+    fb = address_space_map(&address_space_memory, s->so_gpa, &len, true,
+                           MEMTXATTRS_UNSPECIFIED);
+    if (!fb) {
+        return false;
+    }
+    if (len < (hwaddr)s->so_pitch * s->so_fbh) {
+        address_space_unmap(&address_space_memory, fb, len, false, 0);
+        return false;
+    }
+    foreign = so_verify ? false : so_foreign(s, fb);
+    if (foreign && s->so_mode_us && t0 - s->so_mode_us < SO_MODE_GRACE_US) {
+        /* the guest's clear of a new mode's framebuffer: the frames win, all of them */
+        foreign = false;
+        r[0] = r[1] = 0;
+        r[2] = s->so_fbw;
+        r[3] = s->so_fbh;
+    }
+    if (so_verify && s->so_last >= 0) {
+        /* VRAM holds the copy path's result of the same frames: compare */
+        size_t need = (size_t)row * s->so_fbh;
+        int n = 0, bb[4] = { 0, 0, 0, 0 };
+        if (s->staging_size < need) {
+            g_free(s->staging);
+            s->staging = g_malloc(need);
+            s->staging_size = need;
+        }
+        make_current(s, s->so_group->rc);
+        if (s->so_bsync[s->so_last]) {
+            glWaitSync(s->so_bsync[s->so_last], 0, GL_TIMEOUT_IGNORED);
+        }
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, s->so_rd_fbo);
+        glReadBuffer(GL_COLOR_ATTACHMENT0);
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+        glReadPixels(0, 0, s->so_fbw, s->so_fbh, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+                     s->staging);
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, 0);
+        make_current(s, prev);
+        for (y = 0; y < (int)s->so_fbh; y++) {
+            const uint32_t *a = (const uint32_t *)(s->staging + (size_t)y * row);
+            const uint32_t *v = (const uint32_t *)(fb + (size_t)y * s->so_pitch);
+            if (!memcmp(a, v, row)) {
+                continue;
+            }
+            for (x = 0; x < (int)s->so_fbw; x++) {
+                if ((a[x] ^ v[x]) & 0xffffff) {
+                    so_rect_add(bb, x, y, x + 1, y + 1);
+                    n++;
+                }
+            }
+        }
+        fprintf(stderr, "qegl: zero-copy verify: %d pixels differ from VRAM%s", n,
+                n ? "" : "\n");
+        if (n) {
+            static int dumps;
+            const char *dir = getenv("QEGL_SO_VERIFY_DUMP");
+            fprintf(stderr, " in %d,%d-%d,%d\n", bb[0], bb[1], bb[2], bb[3]);
+            if (dir && dumps < 8) {
+                int k;
+                dumps++;
+                for (k = 0; k < 2; k++) {
+                    g_autofree char *fn = g_strdup_printf("%s/verify-%d-%s.ppm", dir, dumps,
+                                                          k ? "vram" : "zerocopy");
+                    FILE *df = fopen(fn, "wb");
+                    if (!df) {
+                        continue;
+                    }
+                    fprintf(df, "P6\n%u %u\n255\n", s->so_fbw, s->so_fbh);
+                    for (y = 0; y < (int)s->so_fbh; y++) {
+                        const uint8_t *px = k ? fb + (size_t)y * s->so_pitch
+                                              : s->staging + (size_t)y * row;
+                        for (x = 0; x < (int)s->so_fbw; x++) {
+                            uint8_t c[3] = { px[x * 4 + 2], px[x * 4 + 1], px[x * 4] };
+                            fwrite(c, 1, 3, df);
+                        }
+                    }
+                    fclose(df);
+                }
+            }
+        }
+        r[0] = r[1] = r[2] = r[3] = 0;
+    } else if (!so_rect_empty(r) && s->so_last >= 0) {
+        size_t need = (size_t)row * s->so_fbh;
+        r[0] = MAX(r[0], 0);
+        r[1] = MAX(r[1], 0);
+        r[2] = MIN(r[2], (int)s->so_fbw);
+        r[3] = MIN(r[3], (int)s->so_fbh);
+        w = r[2] - r[0];
+        h = r[3] - r[1];
+        if (s->staging_size < need) {
+            g_free(s->staging);
+            s->staging = g_malloc(need);
+            s->staging_size = need;
+        }
+        make_current(s, s->so_group->rc);
+        if (s->so_bsync[s->so_last]) {
+            glWaitSync(s->so_bsync[s->so_last], 0, GL_TIMEOUT_IGNORED);
+        }
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, s->so_rd_fbo);   /* so_frame */
+        glReadBuffer(GL_COLOR_ATTACHMENT0);
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+        /* texture rows are top-down: they come out in framebuffer order */
+        glReadPixels(r[0], r[1], w, h, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, s->staging);
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, 0);
+        for (y = 0; y < h; y++) {
+            uint8_t *dst = fb + (size_t)(r[1] + y) * s->so_pitch + (size_t)r[0] * 4;
+            uint8_t *sh = s->so_shadow + (size_t)(r[1] + y) * row + (size_t)r[0] * 4;
+            const uint8_t *src = s->staging + (size_t)y * w * 4;
+            if (!foreign) {
+                memcpy(dst, src, (size_t)w * 4);
+                memcpy(sh, src, (size_t)w * 4);
+                continue;
+            }
+            for (x = 0; x < w; x++) {
+                /* a pixel the guest changed stays */
+                if (((uint32_t *)dst)[x] == ((uint32_t *)sh)[x]) {
+                    ((uint32_t *)dst)[x] = ((const uint32_t *)src)[x];
+                    ((uint32_t *)sh)[x] = ((const uint32_t *)src)[x];
+                }
+            }
+        }
+        make_current(s, prev);
+        r[0] = r[1] = r[2] = r[3] = 0;
+        s->st_so_syncs++;
+    }
+    address_space_unmap(&address_space_memory, fb, len, true, len);
+    s->so_last_sync_us = g_get_monotonic_time();
+    s->st_so_sync_us += s->so_last_sync_us - t0;
+    if (foreign && qegl_debug) {
+        qlog("qegl: zero-copy: the guest wrote into VRAM; frames go through VRAM "
+                "for a while\n");
+    }
+    return foreign;
+}
+
+/* frames go through VRAM again: bring it up to date, show the console surface */
+static void so_stop(QEGLState *s)
+{
+    QemuD3DScanout *sc = s->so_sc;
+    int i;
+
+    if (!s->so_active) {
+        return;
+    }
+    if (so_vram_sync(s)) {
+        s->so_holdoff_until_us = g_get_monotonic_time() + SO_HOLDOFF_US;
+    }
+    /* frames not handed over yet would bring the display back to the ring */
+    for (i = 0; i < s->so_npend; i++) {
+        sc->ops->release(sc->sink, s->so_ring, s->so_pend[i].buf);
+    }
+    s->so_npend = 0;
+    sc->ops->disable(sc->sink);
+    s->so_active = false;
+    qatomic_set(&s->so_active_pub, false);
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+        s->so_full[i] = true;       /* frames through VRAM do not reach the buffers */
+    }
+    if (qegl_debug) {
+        qlog("qegl: zero-copy scanout off\n");
+    }
+}
+
+/* drop the ring and our GL objects for it */
+static void so_release(QEGLState *s)
+{
+    QEGLGroup *g = s->so_group;
+    HGLRC prev = s->current;
+    int i;
+
+    if (!s->so_ring) {
+        return;
+    }
+    so_stop(s);
+    if (g && g->rc) {
+        /* detach before deleting (NVIDIA: see qegl_fbo_detach_all) */
+        for (i = 0; i < QEGL_MAX_CTX; i++) {
+            QEGLContext *c = &s->ctx[i];
+            if (c->id && c->group == g && c->so_fbo && c->so_fbo_tex) {
+                make_current(s, c->hglrc);
+                glBindFramebufferEXT(GL_FRAMEBUFFER, c->so_fbo);
+                qegl_fbo_detach_all(GL_FRAMEBUFFER);
+                glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
+                c->so_fbo_tex = 0;
+            }
+        }
+        make_current(s, g->rc);
+        if (s->so_rd_fbo) {
+            glBindFramebufferEXT(GL_FRAMEBUFFER, s->so_rd_fbo);
+            qegl_fbo_detach_all(GL_FRAMEBUFFER);
+            glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
+            glDeleteFramebuffers(1, &s->so_rd_fbo);
+        }
+        for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+            if (s->so_bsync[i]) {
+                glDeleteSync(s->so_bsync[i]);
+            }
+        }
+        glDeleteTextures(QEMU_D3D_SCANOUT_BUFFERS, s->so_tex);
+        glDeleteTextures(1, &s->so_frame);
+        glDeleteMemoryObjectsEXT(QEMU_D3D_SCANOUT_BUFFERS, s->so_mem);
+        if (s->so_sem) {
+            glDeleteSemaphoresEXT(1, &s->so_sem);
+        }
+        glFinish();
+        if (prev && prev != g->rc) {
+            make_current(s, prev);
+        }
+    }
+    s->so_sc->ops->ring_destroy(s->so_sc->sink, s->so_ring);
+    s->so_ring = NULL;
+    s->so_group = NULL;
+    s->so_sem = 0;
+    s->so_rd_fbo = s->so_frame = 0;
+    memset(s->so_mem, 0, sizeof(s->so_mem));
+    memset(s->so_tex, 0, sizeof(s->so_tex));
+    memset(s->so_bsync, 0, sizeof(s->so_bsync));
+    s->so_last = -1;
+}
+
+/* import a new ring for a w x h framebuffer into group g */
+static bool so_setup(QEGLState *s, QemuD3DScanout *sc, QEGLGroup *g, uint32_t w, uint32_t h)
+{
+    QemuD3DScanoutRing *ring = sc->ops->ring_create(sc->sink, w, h);
+    HGLRC prev = s->current;
+    GLint dedicated = GL_TRUE;
+    GLenum err;
+    int i;
+
+    if (!ring) {
+        s->so_retry_us = g_get_monotonic_time() + SO_HOLDOFF_US;
+        return false;
+    }
+    if (memcmp(&ring->adapter, s->so_luid, sizeof(s->so_luid))) {
+        warn_report("qegl: the display runs on another GPU: no zero-copy scanout");
+        sc->ops->ring_destroy(sc->sink, ring);
+        s->so_failed = true;
+        return false;
+    }
+    make_current(s, g->rc);
+    while (glGetError() != GL_NO_ERROR) {
+    }
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+        glCreateMemoryObjectsEXT(1, &s->so_mem[i]);
+        glMemoryObjectParameterivEXT(s->so_mem[i], GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicated);
+        /* size 0: NVIDIA rejects the storage with w*h*4 (the allocation is padded) */
+        glImportMemoryWin32HandleEXT(s->so_mem[i], 0, GL_HANDLE_TYPE_D3D11_IMAGE_EXT,
+                                     ring->texture[i]);
+        s->so_tex[i] = internal_name(s);
+        glBindTexture(GL_TEXTURE_2D, s->so_tex[i]);
+        glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, w, h, s->so_mem[i], 0);
+        s->so_full[i] = true;
+        memset(s->so_need[i], 0, sizeof(s->so_need[i]));
+    }
+    s->so_frame = internal_name(s);
+    glBindTexture(GL_TEXTURE_2D, s->so_frame);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);
+    glBindTexture(GL_TEXTURE_2D, 0);
+    s->so_rd_fbo = qegl_fbo_name_next++;
+    glBindFramebufferEXT(GL_FRAMEBUFFER, s->so_rd_fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s->so_frame, 0);
+    glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
+    if (s->so_use_sem) {
+        glGenSemaphoresEXT(1, &s->so_sem);
+        glImportSemaphoreWin32HandleEXT(s->so_sem, GL_HANDLE_TYPE_D3D12_FENCE_EXT, ring->fence);
+    }
+    err = glGetError();
+    glFlush();
+    make_current(s, prev);
+    s->so_sc = sc;
+    s->so_ring = ring;
+    s->so_group = g;
+    s->so_value = 0;
+    s->so_last = -1;
+    s->so_mode_us = g_get_monotonic_time();
+    if (err != GL_NO_ERROR) {
+        error_report("qegl: importing the display's shared textures failed (GL error 0x%x): "
+                     "no zero-copy scanout", err);
+        so_release(s);
+        s->so_failed = true;
+        return false;
+    }
+    info_report("qegl: zero-copy scanout: %ux%u ring imported", w, h);
+    return true;
+}
+
+/* Mesa: hand over frames whose blit has finished (waits up to wait_ns for the oldest) */
+static void so_poll(QEGLState *s, GLuint64 wait_ns)
+{
+    HGLRC prev = s->current;
+    bool switched = false;
+
+    while (s->so_npend) {
+        GLenum r;
+        if (!switched && !rc_in_group(s, s->current, s->so_group)) {
+            make_current(s, s->so_group->rc);
+            switched = true;
+        }
+        r = glClientWaitSync(s->so_pend[0].sync, GL_SYNC_FLUSH_COMMANDS_BIT, wait_ns);
+        if (r == GL_TIMEOUT_EXPIRED) {
+            break;
+        }
+        s->so_sc->ops->publish(s->so_sc->sink, s->so_ring, s->so_pend[0].buf, 0,
+                               s->so_pend[0].rect[0], s->so_pend[0].rect[1],
+                               s->so_pend[0].rect[2] - s->so_pend[0].rect[0],
+                               s->so_pend[0].rect[3] - s->so_pend[0].rect[1],
+                               s->so_pend[0].frame_ns);
+        s->so_npend--;
+        memmove(&s->so_pend[0], &s->so_pend[1], s->so_npend * sizeof(s->so_pend[0]));
+        wait_ns = 0;
+    }
+    if (switched) {
+        make_current(s, prev);
+    }
+}
+
+/*
+ * The presented rectangles (framebuffer coordinates, clipped to the surface) go from surface
+ * sf (placed at bx,by) into the accumulated frame - one by one: between them the surface
+ * holds stale pixels. Buffer b then gets the area r it lacks from there.
+ */
+static void so_blit(QEGLExec *e, QEGLSurface *sf, int b, const struct qegl_rect *rects,
+                    uint32_t n, const int *r, int bx, int by)
+{
+    QEGLState *s = e->s;
+    QEGLGroup *g = sf->group;
+    bool in_ctx = e->ctx && e->ctx->group == g && s->current == e->ctx->hglrc;
+    int idx = (sf->flags & QEGL_SURF_DOUBLE) ? 1 : 0;      /* front buffer */
+    GLint prev_rfb = 0, prev_dfb = 0;
+    GLboolean scissor;
+    uint32_t i;
+
+    if (!n) {
+        goto copy;
+    }
+    if (in_ctx) {
+        QEGLContext *c = e->ctx;
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_rfb);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_dfb);
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, c->rd_fbo);
+        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
+                               sf->tex[idx], 0);
+        c->rd_surf = sf->id;
+        if (!c->so_fbo) {
+            c->so_fbo = qegl_fbo_name_next++;
+        }
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, c->so_fbo);
+        if (c->so_fbo_tex != s->so_frame) {
+            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                                   s->so_frame, 0);
+            c->so_fbo_tex = s->so_frame;
+        }
+    } else {
+        group_enter(s, g);
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, g->read_fbo);
+        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
+                               sf->tex[idx], 0);
+        g->rd_surf = sf->id;
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, s->so_rd_fbo);  /* so_frame */
+    }
+    glReadBuffer(GL_COLOR_ATTACHMENT0);
+    scissor = glIsEnabled(GL_SCISSOR_TEST);
+    if (scissor) {
+        glDisable(GL_SCISSOR_TEST);
+    }
+    /* surface: GL rows bottom-up, content in the bottom-left corner; buffer: rows top-down */
+    for (i = 0; i < n; i++) {
+        int x0 = rects[i].x, y0 = rects[i].y;
+        int x1 = x0 + (int)rects[i].w, y1 = y0 + (int)rects[i].h;
+        glBlitFramebuffer(x0 - bx, sf->h - (y1 - by), x1 - bx, sf->h - (y0 - by),
+                          x0, y1, x1, y0, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    }
+    if (scissor) {
+        glEnable(GL_SCISSOR_TEST);
+    }
+    if (in_ctx) {
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER, prev_rfb);
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, prev_dfb);
+    } else {
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
+    }
+copy:
+    if (r[2] > r[0] && r[3] > r[1]) {
+        glCopyImageSubData(s->so_frame, GL_TEXTURE_2D, 0, r[0], r[1], 0,
+                           s->so_tex[b], GL_TEXTURE_2D, 0, r[0], r[1], 0,
+                           r[2] - r[0], r[3] - r[1], 1);
+    }
+}
+
+/* present a frame (damage d, framebuffer coordinates) through the ring; false: use VRAM */
+static bool so_present(QEGLExec *e, QEGLSurface *sf, uint64_t gpa, uint32_t pitch,
+                       uint32_t fbw, uint32_t fbh, int bx, int by,
+                       const struct qegl_rect *rects, uint32_t n, const int *d,
+                       int64_t frame_ns)
+{
+    QEGLState *s = e->s;
+    QemuD3DScanout *sc = qatomic_read(&s->so_avail);
+    struct qegl_shm_header *hdr = (struct qegl_shm_header *)s->shm;
+    int64_t now = g_get_monotonic_time();
+    int b, i, r[4], tries;
+
+    if (!s->scanout || s->so_failed || !sc || !hdr->hw_cursor ||
+        now < s->so_holdoff_until_us || pitch != fbw * 4 || !so_check(s)) {
+        goto vram;
+    }
+    if (s->so_ring && (s->so_sc != sc || s->so_group != sf->group ||
+                       s->so_ring->width != fbw || s->so_ring->height != fbh ||
+                       s->so_gpa != gpa)) {
+        /* the last sync of the old mode must not overwrite what the guest has there now */
+        s->so_mode_us = 0;
+        so_release(s);
+    }
+    if (!s->so_ring) {
+        if (now < s->so_retry_us || !so_setup(s, sc, sf->group, fbw, fbh)) {
+            goto vram;
+        }
+        s->so_gpa = gpa;
+    }
+    b = sc->ops->acquire(sc->sink, s->so_ring);
+    for (tries = 0; b == -1 && tries < 40; tries++) {
+        /* every buffer is held: frames still in flight here, or the display is copying */
+        if (s->so_npend) {
+            so_poll(s, 2000000);
+        } else {
+            g_usleep(500);
+        }
+        b = sc->ops->acquire(sc->sink, s->so_ring);
+    }
+    if (b < 0) {
+        /* the display shows another mode right now (or never let go of a buffer) */
+        s->so_retry_us = now + SO_RETRY_US;
+        goto vram;
+    }
+    if (!s->so_active) {
+        /* VRAM is current (frames went through it so far): remember what is there */
+        hwaddr len = (hwaddr)pitch * fbh;
+        uint8_t *fb = address_space_map(&address_space_memory, gpa, &len, false,
+                                        MEMTXATTRS_UNSPECIFIED);
+        size_t need = (size_t)fbw * fbh * 4;
+        if (!fb || len < (hwaddr)pitch * fbh) {
+            if (fb) {
+                address_space_unmap(&address_space_memory, fb, len, false, 0);
+            }
+            sc->ops->release(sc->sink, s->so_ring, b);
+            goto vram;
+        }
+        if (s->so_shadow_size < need) {
+            g_free(s->so_shadow);
+            s->so_shadow = g_malloc(need);
+            s->so_shadow_size = need;
+        }
+        memcpy(s->so_shadow, fb, need);     /* pitch == fbw * 4 */
+        address_space_unmap(&address_space_memory, fb, len, false, 0);
+        {
+            HGLRC prev = s->current;
+            make_current(s, s->so_group->rc);
+            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+            glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+            glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+            glBindTexture(GL_TEXTURE_2D, s->so_frame);
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fbw, fbh, GL_BGRA,
+                            GL_UNSIGNED_INT_8_8_8_8_REV, s->so_shadow);
+            glBindTexture(GL_TEXTURE_2D, 0);
+            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);      /* as group_enter() set it */
+            glFlush();
+            make_current(s, prev);
+        }
+        for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+            s->so_full[i] = true;
+        }
+        s->so_gpa = gpa;
+        s->so_pitch = pitch;
+        s->so_fbw = fbw;
+        s->so_fbh = fbh;
+        memset(s->so_stale, 0, sizeof(s->so_stale));
+        s->so_last_sync_us = s->so_last_check_us = now;
+        s->so_active = true;
+        qatomic_set(&s->so_active_pub, true);
+        if (qegl_debug) {
+            qlog("qegl: zero-copy scanout on (%ux%u)\n", fbw, fbh);
+        }
+    }
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+        so_rect_add(s->so_need[i], d[0], d[1], d[2], d[3]);
+    }
+    memcpy(r, s->so_need[b], sizeof(r));
+    if (s->so_full[b]) {
+        r[0] = r[1] = 0;
+        r[2] = fbw;
+        r[3] = fbh;
+    }
+    r[0] = MAX(r[0], 0);
+    r[1] = MAX(r[1], 0);
+    r[2] = MIN(r[2], (int)fbw);
+    r[3] = MIN(r[3], (int)fbh);
+    so_blit(e, sf, b, rects, n, r, bx, by);
+    memset(s->so_need[b], 0, sizeof(s->so_need[b]));
+    s->so_full[b] = false;
+    if (s->so_bsync[b]) {
+        glDeleteSync(s->so_bsync[b]);
+    }
+    s->so_bsync[b] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+    if (s->so_sem) {
+        GLenum layout = GL_LAYOUT_SHADER_READ_ONLY_EXT;
+        uint64_t v = ++s->so_value;
+        glSemaphoreParameterui64vEXT(s->so_sem, GL_D3D12_FENCE_VALUE_EXT, &v);
+        glSignalSemaphoreEXT(s->so_sem, 0, NULL, 1, &s->so_tex[b], &layout);
+        glFlush();
+        sc->ops->publish(sc->sink, s->so_ring, b, v, d[0], d[1], d[2] - d[0], d[3] - d[1],
+                         frame_ns);
+    } else {
+        glFlush();
+        s->so_pend[s->so_npend].sync = s->so_bsync[b];
+        s->so_pend[s->so_npend].buf = b;
+        memcpy(s->so_pend[s->so_npend].rect, d, sizeof(s->so_pend[0].rect));
+        s->so_pend[s->so_npend].frame_ns = frame_ns;
+        s->so_npend++;
+        so_poll(s, 0);
+    }
+    so_rect_add(s->so_stale, d[0], d[1], d[2], d[3]);
+    s->so_last = b;
+    s->so_last_present_us = now;
+    s->st_so_frames++;
+    s->st_so_blit_us += g_get_monotonic_time() - now;
+    if (!so_verify && now - s->so_last_sync_us >= SO_STALE_US && so_vram_sync(s)) {
+        so_stop(s);
+        s->so_holdoff_until_us = g_get_monotonic_time() + SO_HOLDOFF_US;
+    }
+    return true;
+
+vram:
+    if (s->so_active) {
+        so_stop(s);
+    }
+    return false;
+}
+
+/* render thread, nothing queued: when is zero-copy work due? (ms; 0 now, -1 none) */
+static int so_idle_due_ms(QEGLState *s)
+{
+    int64_t now, due;
+
+    if (s->so_npend) {
+        return 0;
+    }
+    if (!s->so_active) {
+        return -1;
+    }
+    now = g_get_monotonic_time();
+    due = s->so_last_check_us + SO_CHECK_US;
+    if (!so_rect_empty(s->so_stale)) {
+        due = MIN(due, s->so_last_present_us + SO_SETTLE_US);
+    }
+    return due <= now ? 0 : (int)((due - now + 999) / 1000);
+}
+
+static void so_idle(QEGLState *s, bool sync_now)
+{
+    int64_t now;
+
+    if (s->so_npend) {
+        so_poll(s, 1000000);
+    }
+    if (!s->so_active) {
+        return;
+    }
+    now = g_get_monotonic_time();
+    if (sync_now || now >= s->so_last_check_us + SO_CHECK_US ||
+        (!so_rect_empty(s->so_stale) && now >= s->so_last_present_us + SO_SETTLE_US)) {
+        if (so_vram_sync(s)) {
+            so_stop(s);
+            s->so_holdoff_until_us = g_get_monotonic_time() + SO_HOLDOFF_US;
+        }
+    }
+}
+
+/* copy rectangles (framebuffer coordinates) of a surface whose (0,0) is at (bx,by) */
+static int present_rects(QEGLExec *e, uint32_t surf, uint64_t gpa, uint32_t pitch,
+                         uint32_t fbw, uint32_t fbh, int bx, int by,
+                         const struct qegl_rect *rects, uint32_t n)
+{
+    QEGLState *s = e->s;
+    QEGLSurface *sf = qegl_find_surface(s, surf);
+    HGLRC prev = s->current;
+    int64_t frame_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+    int miny = INT_MAX, maxy = INT_MIN, minx = INT_MAX, maxx = INT_MIN;
+    uint32_t i;
+    uint8_t *img, *fb;
+    hwaddr len;
+
+    if (!sf) {
+        return QEGL_ERR_BAD_OBJECT;
+    }
+    if (pitch < fbw * 4 || !fbw || !fbh) {
+        return QEGL_ERR_RANGE;
+    }
+    /* clip all rectangles first */
+    struct qegl_rect *cl = g_new(struct qegl_rect, n);
+    uint32_t m = 0;
+    for (i = 0; i < n; i++) {
+        int x0 = MAX(rects[i].x, MAX(bx, 0));
+        int y0 = MAX(rects[i].y, MAX(by, 0));
+        int x1 = MIN(rects[i].x + rects[i].w, MIN(bx + (int)sf->w, (int)fbw));
+        int y1 = MIN(rects[i].y + rects[i].h, MIN(by + (int)sf->h, (int)fbh));
+        if (x1 <= x0 || y1 <= y0) {
+            continue;
+        }
+        cl[m].x = x0;
+        cl[m].y = y0;
+        cl[m].w = x1 - x0;
+        cl[m].h = y1 - y0;
+        miny = MIN(miny, y0);
+        maxy = MAX(maxy, y1);
+        minx = MIN(minx, x0);
+        maxx = MAX(maxx, x1);
+        m++;
+    }
+    if (!m) {
+        g_free(cl);
+        return 0;
+    }
+    int64_t t0 = g_get_monotonic_time();
+    if (so_verify < 0) {
+        so_verify = getenv("QEGL_SO_VERIFY") && atoi(getenv("QEGL_SO_VERIFY"));
+    }
+    {
+        int dmg[4] = { minx, miny, maxx, maxy };
+        if (so_present(e, sf, gpa, pitch, fbw, fbh, bx, by, cl, m, dmg, frame_ns) &&
+            !so_verify) {
+            /* zero-copy: the display shows it straight from the GPU, VRAM catches up later */
+            g_free(cl);
+            make_current(s, prev);
+            s->frames++;
+            s->st_frames++;
+            s->st_pixels += (uint64_t)sf->w * sf->h;
+            fb = NULL;
+            goto stats;
+        }
+    }
+    /* the region covering all rectangles, in the surface's GL coordinates (rows bottom-up) */
+    img = surf_readback(e, sf, 0, minx - bx, (int)sf->h - (maxy - by), maxx - minx,
+                        maxy - miny);
+    int64_t t1 = g_get_monotonic_time();
+    len = (hwaddr)pitch * (maxy - miny);
+    fb = address_space_map(&address_space_memory, gpa + (uint64_t)miny * pitch, &len, true,
+                           MEMTXATTRS_UNSPECIFIED);
+    if (fb && len >= (hwaddr)pitch * (maxy - miny)) {
+        for (i = 0; i < m; i++) {
+            int r;
+            for (r = 0; r < cl[i].h; r++) {
+                int ty = cl[i].y + r;                 /* framebuffer row */
+                int sy = sf->h - 1 - (ty - by);       /* GL row of the surface */
+                memcpy(fb + (size_t)(ty - miny) * pitch + (size_t)cl[i].x * 4,
+                       img + ((size_t)(sy - rb_y0) * rb_w + (cl[i].x - bx - rb_x0)) * 4,
+                       (size_t)cl[i].w * 4);
+            }
+        }
+    }
+    readback_done(e, sf);
+    if (fb) {
+        address_space_unmap(&address_space_memory, fb, len, true, len);
+        qatomic_set(&s->lat_frame_ns, frame_ns);
+        if (s->display_update) {
+            qegl_request_update(s);     /* show the frame now, not at the next refresh */
+        }
+    }
+    g_free(cl);
+    make_current(s, prev);
+    s->frames++;
+    s->st_read_us += t1 - t0;
+    s->st_copy_us += g_get_monotonic_time() - t1;
+    /* per-second statistics: presents (= composited frames) and where the host time went */
+    s->st_frames++;
+    s->st_pixels += (uint64_t)sf->w * sf->h;
+stats:
+    if (qegl_debug) {
+        int64_t now = g_get_monotonic_time();
+        int64_t d = now - s->st_prev_present_us;
+        if (s->st_prev_present_us && d < 100000) {
+            uint32_t vbl = qatomic_read(&s->vbl_mhz);
+            int64_t period = vbl ? 1000000000ll / vbl : 16667;   /* us */
+            s->st_anim_us += d;
+            s->st_anim_n++;
+            if (d > period * 3 / 2) {
+                s->st_anim_slow++;
+            }
+            s->st_anim_hist[d * 2 < period ? 0 : d * 2 < period * 3 ? 1 :
+                            d * 2 < period * 5 ? 2 : 3]++;
+        }
+        s->st_prev_present_us = now;
+        if (!s->st_last_us) {
+            s->st_last_us = now;
+        } else if (now - s->st_last_us >= 1000000) {
+            double nf = s->st_frames;
+            qlog("qegl: %3.0f fps | per frame: ring exec %.2f ms (readback %.2f, "
+                    "fb copy %.2f), %.0f Kpix read; display updates %d x %.2f ms (present->display "
+                    "%.2f ms avg %.2f max); cursor %u defs %u moves; "
+                    "anim %.1f fps (%d intervals, %d > 1.5 VBL; VBL multiples <0.5/1/2/3+: "
+                    "%d/%d/%d/%d)\n",
+                    nf * 1e6 / (now - s->st_last_us),
+                    s->st_ring_us / nf / 1e3, s->st_read_us / nf / 1e3,
+                    s->st_copy_us / nf / 1e3, s->st_pixels / nf / 1e3, s->st_disp_n,
+                    s->st_disp_n ? s->st_disp_us / 1e3 / s->st_disp_n : 0.0,
+                    s->st_lat_n ? s->st_lat_us / 1e3 / s->st_lat_n : 0.0,
+                    s->st_lat_max_us / 1e3,
+                    (unsigned) qatomic_xchg(&s->st_cursor_defs, 0),
+                    (unsigned) qatomic_xchg(&s->st_cursor_moves, 0),
+                    s->st_anim_us ? s->st_anim_n * 1e6 / s->st_anim_us : 0.0,
+                    s->st_anim_n, s->st_anim_slow, s->st_anim_hist[0], s->st_anim_hist[1],
+                    s->st_anim_hist[2], s->st_anim_hist[3]);
+            if (s->st_so_frames || s->st_so_syncs || s->so_active) {
+                qlog("qegl: zero-copy %s: %" PRIu64 " frames (%.3f ms each), %"
+                        PRIu64 " VRAM syncs (%.2f ms each)\n", s->so_active ? "on" : "off",
+                        s->st_so_frames, s->st_so_frames ?
+                        s->st_so_blit_us / 1e3 / s->st_so_frames : 0.0, s->st_so_syncs,
+                        s->st_so_syncs ? s->st_so_sync_us / 1e3 / s->st_so_syncs : 0.0);
+                s->st_so_frames = s->st_so_syncs = 0;
+                s->st_so_blit_us = s->st_so_sync_us = 0;
+            }
+            {
+                /* host CPU over the same second: this render thread, and the whole QEMU
+                   process (vCPUs, main loop, and the GL driver's own threads) */
+                static uint64_t last_thr, last_proc;
+                FILETIME c, x, k, u;
+                uint64_t thr = 0, proc = 0;
+                if (GetThreadTimes(GetCurrentThread(), &c, &x, &k, &u)) {
+                    thr = ((uint64_t)k.dwHighDateTime << 32 | k.dwLowDateTime) +
+                          ((uint64_t)u.dwHighDateTime << 32 | u.dwLowDateTime);
+                }
+                if (GetProcessTimes(GetCurrentProcess(), &c, &x, &k, &u)) {
+                    proc = ((uint64_t)k.dwHighDateTime << 32 | k.dwLowDateTime) +
+                           ((uint64_t)u.dwHighDateTime << 32 | u.dwLowDateTime);
+                }
+                if (last_thr) {
+                    double secs = (now - s->st_last_us) / 1e6;
+                    qlog("qegl: cpu %.0f frames in %.3f s, render thread %.1f ms, "
+                            "process %.1f ms\n", nf, secs, (thr - last_thr) / 1e4,
+                            (proc - last_proc) / 1e4);
+                }
+                last_thr = thr;
+                last_proc = proc;
+            }
+            memset(s->st_anim_hist, 0, sizeof(s->st_anim_hist));
+            s->st_anim_us = 0;
+            s->st_anim_n = s->st_anim_slow = 0;
+            s->st_disp_us = 0;
+            s->st_disp_n = 0;
+            qatomic_set(&s->st_lat_us, 0);
+            qatomic_set(&s->st_lat_n, 0);
+            qatomic_set(&s->st_lat_max_us, 0);
+            s->st_read_us = s->st_copy_us = s->st_ring_us = 0;
+            s->st_frames = 0;
+            s->st_pixels = 0;
+            s->st_last_us = now;
+        }
+    }
+    if (qegl_debug > 1 && s->frames <= 20) {
+        qlog("qegl: present #%" PRIu64 " surf 0x%x %ux%u rects %u (rows %d-%d) %s\n",
+                s->frames, surf, sf->w, sf->h, m, miny, maxy, s->so_active ? "zero-copy" :
+                fb ? "ok" : "MAP FAILED");
+    }
+    return fb || s->so_active ? 0 : QEGL_ERR_RANGE;
+}
+
+static int op_present(QEGLExec *e, const struct qegl_present *p)
+{
+    QEGLSurface *sf = qegl_find_surface(e->s, p->surf);
+    struct qegl_rect r;
+    if (!sf) {
+        return QEGL_ERR_BAD_OBJECT;
+    }
+    /* source rect (x,y,w,h) of the surface goes to (dst_x,dst_y) */
+    r.x = p->dst_x;
+    r.y = p->dst_y;
+    r.w = p->w;
+    r.h = p->h;
+    return present_rects(e, p->surf, ((uint64_t)p->dst_gpa_hi << 32) | p->dst_gpa_lo,
+                         p->dst_pitch, p->dst_w, p->dst_h,
+                         (int)p->dst_x - (int)p->x, (int)p->dst_y - (int)p->y, &r, 1);
+}
+
+/* CGLTexImagePBuffer: the pbuffer (a surface of the same share group) becomes the image of the
+   texture bound to `target` in the current context */
+static int op_surf_teximage(QEGLExec *e, const uint32_t *a)
+{
+    QEGLContext *c = e->ctx;
+    QEGLSurface *sf = qegl_find_surface(e->s, a[0]);
+    GLint prev_read = 0;
+    GLenum ifmt = a[2];
+    uint32_t w, h;
+    if (!c || !sf || sf->group != c->group) {
+        return QEGL_ERR_BAD_OBJECT;
+    }
+    w = MIN(a[3], sf->w);
+    h = MIN(a[4], sf->h);
+    if (ifmt == GL_RGBA || ifmt == 4) {
+        ifmt = GL_RGBA8;
+    } else if (ifmt == GL_RGB || ifmt == 3) {
+        ifmt = GL_RGB8;
+    }
+    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read);
+    glBindFramebufferEXT(GL_READ_FRAMEBUFFER, c->rd_fbo);
+    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,
+                           sf->tex[0], 0);
+    c->rd_surf = sf->id;
+    glReadBuffer(GL_COLOR_ATTACHMENT0);
+    glCopyTexImage2D(a[1], 0, ifmt, 0, 0, w, h, 0);
+    glBindFramebufferEXT(GL_READ_FRAMEBUFFER, prev_read);
+    return 0;
+}
+
+static int op_surf_read(QEGLExec *e, const struct qegl_surf_read *r)
+{
+    QEGLSurface *sf = qegl_find_surface(e->s, r->surf);
+    HGLRC prev = e->s->current;
+    uint64_t need;
+    uint8_t *img;
+    uint32_t i;
+    if (!sf) {
+        return QEGL_ERR_BAD_OBJECT;
+    }
+    if (!r->w || !r->h || r->x + r->w > sf->w || r->y + r->h > sf->h ||
+        r->reply_pitch < r->w * 4) {
+        return QEGL_ERR_RANGE;
+    }
+    need = (uint64_t)r->reply_pitch * (r->h - 1) + r->w * 4;
+    if (r->reply_off >= e->rbuf_size || need > e->rbuf_size - r->reply_off) {
+        return QEGL_ERR_RANGE;
+    }
+    img = surf_readback(e, sf, r->buffer == 1, r->x, sf->h - (r->y + r->h), r->w, r->h);
+    for (i = 0; i < r->h; i++) {
+        uint32_t sy = sf->h - 1 - (r->y + i);
+        memcpy(e->rbuf + r->reply_off + (uint64_t)i * r->reply_pitch,
+               img + ((size_t)(sy - rb_y0) * rb_w + (r->x - rb_x0)) * 4, (size_t)r->w * 4);
+    }
+    readback_done(e, sf);
+    make_current(e->s, prev);
+    return 0;
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* control commands */
+
+static void process_ring(QEGLState *s, int ri, bool limited, uint32_t limit);
+
+static int exec_control(QEGLExec *e, uint32_t op, const uint32_t *a, uint32_t n)
+{
+    QEGLState *s = e->s;
+    QEGLContext *c;
+    QEGLSurface *sf;
+    QEGLGroup *g;
+    int i;
+
+#define NEED(k) do { if (n < (k)) return QEGL_ERR_BAD_CMD; } while (0)
+    switch (op) {
+    case QEGL_OP_NOP:
+        return 0;
+    case QEGL_OP_RING_BARRIER: {
+        /* run ring a[0] up to its published byte count a[1] before going on here. In replay
+           the other ring's commands follow in the recording (they were recorded as they ran) */
+        static int depth;
+        struct qegl_ring_desc *od;
+        NEED(2);
+        if (s->replay || a[0] >= QEGL_MAX_RINGS || (int)a[0] == e->ring || depth > 1 ||
+            s->rings[a[0]].active) {
+            return 0;       /* never re-enter a ring that is executing up the stack */
+        }
+        od = (struct qegl_ring_desc *)(s->shm + QEGL_RING_TABLE_OFF + 64 * a[0]);
+        if ((int32_t)(qatomic_load_acquire(&od->head) - a[1]) < 0 && (od->gpa_lo | od->gpa_hi)) {
+            depth++;
+            /* only up to the count: past it, that ring may already depend on commands of
+               this one after the barrier (running them here and again on return executed
+               them twice: duplicate CTX_CREATE) */
+            process_ring(s, a[0], true, a[1]);
+            depth--;
+            /* the other ring may have destroyed our context: look it up again rather than
+               switching back to a deleted HGLRC (Camino WebGL crash, 2026-10-19 17:18) */
+            e->ctx = find_ctx(s, s->rings[e->ring].cur_ctx);
+            make_current(s, e->ctx ? e->ctx->hglrc : s->master);
+        }
+        return 0;
+    }
+    case QEGL_OP_CTX_CREATE:
+        NEED(3);
+        if (!a[0] || find_ctx(s, a[0])) {
+            return QEGL_ERR_BAD_OBJECT;
+        }
+        for (i = 0; i < QEGL_MAX_CTX && s->ctx[i].id; i++) {
+        }
+        if (i == QEGL_MAX_CTX) {
+            return QEGL_ERR_RANGE;
+        }
+        g = group_get(s, a[1]);
+        if (!g) {
+            return QEGL_ERR_GL;
+        }
+        c = &s->ctx[i];
+        memset(c, 0, sizeof(*c));
+        c->hglrc = create_rc(s, g->rc);
+        if (!c->hglrc) {
+            error_report("qegl: creating a shared GL context failed (%lu)", GetLastError());
+            if (c->hglrc) {
+                wglDeleteContext(c->hglrc);
+            }
+            c->hglrc = NULL;
+            if (!g->refs) {
+                g->refs = 1;
+                group_unref(s, g);
+            }
+            return QEGL_ERR_GL;
+        }
+        g->refs++;
+        c->id = a[0];
+        c->prog_error_pos = -1;
+        c->surf_fbo = qegl_fbo_name_next;
+        c->rd_fbo = qegl_fbo_name_next + 1;
+        qegl_fbo_name_next += 2;
+        make_current(s, c->hglrc);
+        qegl_enable_gl_debug();
+        c->group = g;
+        c->owner_ring = e->ring;
+        c->draw_buffer = c->read_buffer = GL_BACK;
+        make_current(s, c->hglrc);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        {
+            static unsigned n_created;
+            if (n_created++ < 8 || n_created % 64 == 0) {
+                raise_driver_threads();
+            }
+        }
+        if (e->ctx) {
+            make_current(s, e->ctx->hglrc);
+        }
+        if (qegl_debug) {
+            qlog("qegl: ring %d ctx %u create (group %u)\n", e->ring, a[0], a[1]);
+        }
+        return 0;
+    case QEGL_OP_CTX_DESTROY:
+        NEED(1);
+        c = find_ctx(s, a[0]);
+        if (!c) {
+            return QEGL_ERR_BAD_OBJECT;
+        }
+        /* current on any ring (another thread, or a ring up the stack in a barrier): that
+           ring's next GL commands get BAD_OBJECT instead of running on a freed slot */
+        for (i = 0; i < QEGL_MAX_RINGS; i++) {
+            if (s->rings[i].cur_ctx == c->id) {
+                s->rings[i].cur_ctx = 0;
+            }
+        }
+        if (e->ctx == c) {
+            e->ctx = NULL;
+        }
+        ctx_free(s, c);
+        return 0;
+    case QEGL_OP_MAKE_CURRENT:
+        NEED(3);
+        c = find_ctx(s, a[0]);
+        if (a[0] && !c) {
+            return QEGL_ERR_BAD_OBJECT;
+        }
+        s->rings[e->ring].cur_ctx = a[0];
+        e->ctx = c;
+        if (c) {
+            c->draw_surf = a[1];
+            c->read_surf = a[2];
+            make_current(s, c->hglrc);
+            if (!c->guest_fbo) {
+                qegl_bind_draw_surface(e);
+                qegl3_fix_read_binding(e);
+            }
+        }
+        return 0;
+    case QEGL_OP_SURF_CREATE:
+        NEED(4);
+        return surf_create(e, a[0], a[1], a[2], a[3]);
+    case QEGL_OP_SURF_RESIZE:
+        NEED(3);
+        sf = qegl_find_surface(s, a[0]);
+        if (!sf || !a[1] || !a[2] || a[1] > 16384 || a[2] > 16384) {
+            return QEGL_ERR_BAD_OBJECT;
+        }
+        surf_set_size(e, sf, a[1], a[2]);
+        return 0;
+    case QEGL_OP_SURF_DESTROY:
+        NEED(1);
+        sf = qegl_find_surface(s, a[0]);
+        if (!sf) {
+            return QEGL_ERR_BAD_OBJECT;
+        }
+        {
+            HGLRC prev = s->current;
+            surf_free(s, sf);
+            if (e->ctx) {
+                make_current(s, prev);
+            }
+        }
+        return 0;
+    case QEGL_OP_SURF_SWAP:
+        NEED(1);
+        sf = qegl_find_surface(s, a[0]);
+        if (!sf) {
+            return QEGL_ERR_BAD_OBJECT;
+        }
+        if (sf->flags & QEGL_SURF_DOUBLE) {
+            GLuint t = sf->tex[0];
+            sf->tex[0] = sf->tex[1];
+            sf->tex[1] = t;
+            sf->gen++;
+            rebind_if_current(e, sf);
+        }
+        glFlush();
+        return 0;
+    case QEGL_OP_SURF_PRESENT:
+        if (n * 4 < sizeof(struct qegl_present)) {
+            return QEGL_ERR_BAD_CMD;
+        }
+        return op_present(e, (const struct qegl_present *)a);
+    case QEGL_OP_SURF_TEXIMAGE:
+        NEED(5);
+        return op_surf_teximage(e, a);
+    case QEGL_OP_SURF_READ:
+        if (n * 4 < sizeof(struct qegl_surf_read)) {
+            return QEGL_ERR_BAD_CMD;
+        }
+        return op_surf_read(e, (const struct qegl_surf_read *)a);
+    case QEGL_OP_SURF_PRESENT_RECTS: {
+        const struct qegl_present_rects *p = (const void *)a;
+        if (n * 4 < sizeof(*p) - sizeof(p->rects) ||
+            p->count > (n * 4 - (sizeof(*p) - sizeof(p->rects))) / sizeof(struct qegl_rect)) {
+            return QEGL_ERR_BAD_CMD;
+        }
+        return present_rects(e, p->surf, ((uint64_t)p->dst_gpa_hi << 32) | p->dst_gpa_lo,
+                             p->dst_pitch, p->dst_w, p->dst_h, p->bounds_x, p->bounds_y,
+                             p->rects, p->count);
+    }
+    }
+#undef NEED
+    return QEGL_ERR_BAD_CMD;
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* ring processing (render thread) */
+
+static struct qegl_ring_desc *ring_desc(QEGLState *s, int i);
+
+/* keep a GL error the host consumed itself for the guest's glGetError */
+void qegl_note_gl_error(QEGLExec *e, GLenum err)
+{
+    struct qegl_ring_desc *rd = ring_desc(e->s, e->ring);
+    if (err != GL_NO_ERROR && !rd->gl_error) {
+        rd->gl_error = err;
+    }
+}
+
+static struct qegl_ring_desc *ring_desc(QEGLState *s, int i)
+{
+    return (struct qegl_ring_desc *)(s->shm + QEGL_RING_TABLE_OFF + 64 * i);
+}
+
+/* Command recording for offline replay (QEGL_RECORD=<file> in QEMU's environment): every
+   executed command in execution order, with its ring, ring offset and ring size, plus ring
+   resets. Replayed by -device qegl,replay=<file> in a QEMU that runs no guest. */
+#define QEGL_REC_CMD   0x43455251u     /* "QREC" */
+#define QEGL_REC_RESET 0x54535251u     /* "QRST" */
+static FILE *qegl_rec;
+
+static void rec_open(void)
+{
+    static bool tried;
+    const char *path = getenv("QEGL_RECORD");
+    if (tried || !path) {
+        return;
+    }
+    tried = true;
+    qegl_rec = fopen(path, "wb");
+    if (qegl_rec) {
+        setvbuf(qegl_rec, NULL, _IOFBF, 1 << 20);
+        info_report("qegl: recording the command stream to %s", path);
+    }
+}
+
+static void rec_write(uint32_t magic, uint32_t ring, uint32_t pos, uint32_t size,
+                      const void *cmd, uint32_t len)
+{
+    uint32_t h[5] = { magic, ring, pos, size, len };
+    if (!qegl_rec) {
+        return;
+    }
+    fwrite(h, sizeof(h), 1, qegl_rec);
+    if (len) {
+        fwrite(cmd, len, 1, qegl_rec);
+    }
+}
+
+/* diagnostics run before a command executes (debug >= 2), live and in replay */
+static void diag_before(QEGLState *s, QEGLExec *e, int ri, uint32_t op,
+                        const struct qegl_cmd *cmd, uint32_t len)
+{
+    if (qegl_debug >= 2 && op == QEGL_OP_GL_BASE + QI_delete_textures && e->ctx &&
+        s->current == e->ctx->hglrc && len >= 16) {
+        /* diagnostics: does the guest delete what our surface FBO has attached? */
+        GLint fb = 0, ty = 0, nm = 0;
+        const uint32_t *aa = (const uint32_t *)(cmd + 1);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb);
+        if (fb) {
+            glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &ty);
+            if (ty == GL_TEXTURE) {
+                glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER,
+                    GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &nm);
+            }
+            for (uint32_t k = 0; k < aa[0] && 8 + 8 + 4 * k < len; k++) {
+                if ((GLint)aa[2 + k] == nm) {
+                    QEGLSurface *dsf = qegl_find_surface(s, e->ctx->draw_surf);
+                    fprintf(stderr, "qegl: ring %d ctx 0x%x deletes texture 0x%x, attached to "
+                            "its draw fbo 0x%x (draw surf 0x%x tex 0x%x fbo_surf 0x%x)\n",
+                            ri, e->ctx->id, nm, fb, e->ctx->draw_surf,
+                            dsf ? dsf->tex[0] : 0, e->ctx->fbo_surf);
+                }
+            }
+        }
+    }
+}
+
+/* diagnostics run after a command executed: QEGL_FBOCHECK, glClear on an incomplete FBO */
+static void diag_after(QEGLState *s, QEGLExec *e, int ri, uint32_t op,
+                       const struct qegl_cmd *cmd, uint32_t len)
+{
+    if (qegl_fbocheck && e->ctx && s->current == e->ctx->hglrc && e->ctx->surf_fbo) {
+        /* diagnostics: report every command after which the current context's surface FBO
+           has something else at colour attachment 0 (direct state access query: no binding) */
+        static GLint last_name[QEGL_MAX_CTX];
+        int ci = e->ctx - s->ctx;
+        GLint ty = 0, nm = 0, fb = 0;
+        glGetNamedFramebufferAttachmentParameteriv(e->ctx->surf_fbo, GL_COLOR_ATTACHMENT0,
+            GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &ty);
+        if (ty == GL_TEXTURE) {
+            glGetNamedFramebufferAttachmentParameteriv(e->ctx->surf_fbo, GL_COLOR_ATTACHMENT0,
+                GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &nm);
+        }
+        if (nm != last_name[ci]) {
+            const uint32_t *aa = (const uint32_t *)(cmd + 1);
+            QEGLSurface *dsf = qegl_find_surface(s, e->ctx->draw_surf);
+            glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb);
+            fprintf(stderr, "qegl: FBOCHECK #%" PRIu64 " ring %d ctx 0x%x colour0 0x%x -> 0x%x "
+                    "after op 0x%x (%s) args %x %x %x; draw surf 0x%x tex 0x%x, draw fbo 0x%x\n",
+                    replay_ncmd, ri, e->ctx->id, last_name[ci], nm, op,
+                    op >= QEGL_OP_GL_BASE ? qegl_gl_name(op - QEGL_OP_GL_BASE) : "control",
+                    len > 8 ? aa[0] : 0, len > 12 ? aa[1] : 0, len > 16 ? aa[2] : 0,
+                    e->ctx->draw_surf, dsf ? dsf->tex[0] : 0, fb);
+            last_name[ci] = nm;
+        }
+    }
+    if (qegl_debug >= 2 && op == QEGL_OP_GL_BASE + QI_clear && e->ctx &&
+        s->current == e->ctx->hglrc) {
+        GLenum st = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
+        if (st != GL_FRAMEBUFFER_COMPLETE) {
+            GLint fb = 0, t0 = 0, ty = 0, db = 0;
+            QEGLSurface *dsf = qegl_find_surface(s, e->ctx->draw_surf);
+            glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb);
+            glGetIntegerv(GL_DRAW_BUFFER, &db);
+            if (fb) {
+                glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER,
+                    GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &ty);
+                if (ty == GL_TEXTURE) {
+                    glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER,
+                        GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &t0);
+                }
+            }
+            fprintf(stderr, "qegl: ring %d ctx 0x%x glClear on incomplete fbo 0x%x (status "
+                    "0x%x, draw buffer 0x%x, color0 type 0x%x name 0x%x): draw surf 0x%x %s "
+                    "tex 0x%x %ux%u storage %ux%u gen %u/%u fbo_surf 0x%x guest fbo %u\n",
+                    ri, e->ctx->id, fb, st, db, ty, t0, e->ctx->draw_surf,
+                    dsf ? (dsf->group == e->ctx->group ? "same group" : "OTHER group")
+                        : "MISSING", dsf ? dsf->tex[0] : 0, dsf ? dsf->w : 0,
+                    dsf ? dsf->h : 0, dsf ? dsf->sw : 0, dsf ? dsf->sh : 0,
+                    dsf ? dsf->gen : 0, e->ctx->fbo_gen, e->ctx->fbo_surf, e->ctx->guest_fbo);
+        }
+    }
+}
+
+/* Heap corruption hunting (property heapcheck=1: validate every process heap after each
+   command of an application ring, 2: after every command): reports the first command after
+   which a heap is corrupt, with the recent command history. */
+static struct {
+    int ring;
+    uint32_t op, len, a[6];
+} heap_hist[64];
+static unsigned heap_hist_n;
+static bool heap_bad;
+
+/* Only the process heap (UCRT malloc = glib, QEMU and a MinGW-built Mesa): validating heaps
+   that drivers created with HEAP_NO_SERIALIZE while their threads use them faults. */
+static bool heaps_valid(int *bad_index)
+{
+    *bad_index = 0;
+    return HeapValidate(GetProcessHeap(), 0, NULL);
+}
+
+static void heap_check(int ri, uint32_t op, uint32_t len, const uint32_t *aa)
+{
+    int bad = -1;
+    unsigned k = heap_hist_n++ % 64, i;
+    heap_hist[k].ring = ri;
+    heap_hist[k].op = op;
+    heap_hist[k].len = len;
+    for (i = 0; i < 6; i++) {
+        heap_hist[k].a[i] = len > 8 + 4 * i ? aa[i] : 0;
+    }
+    if (heap_bad || heaps_valid(&bad)) {
+        return;
+    }
+    heap_bad = true;
+    fprintf(stderr, "qegl: HEAP %d CORRUPT after ring %d op 0x%x (%s) len %u; last commands "
+            "(oldest first):\n", bad, ri, op,
+            op >= QEGL_OP_GL_BASE ? qegl_gl_name(op - QEGL_OP_GL_BASE) : "control", len);
+    for (i = 0; i < 64 && i < heap_hist_n; i++) {
+        unsigned j = (heap_hist_n - MIN(heap_hist_n, 64) + i) % 64;
+        fprintf(stderr, "qegl:   ring %d op 0x%x %s len %u: %x %x %x %x %x %x\n",
+                heap_hist[j].ring, heap_hist[j].op,
+                heap_hist[j].op >= QEGL_OP_GL_BASE ?
+                qegl_gl_name(heap_hist[j].op - QEGL_OP_GL_BASE) : "control", heap_hist[j].len,
+                heap_hist[j].a[0], heap_hist[j].a[1], heap_hist[j].a[2], heap_hist[j].a[3],
+                heap_hist[j].a[4], heap_hist[j].a[5]);
+    }
+    fflush(stderr);
+}
+
+/* Host time per command type (property prof=N: print the top entries every N seconds;
+   QEGL_PROF in the environment is the fixed 5 s profile above) */
+#define PPROF_SLOTS (0x100 + 700)
+static int64_t pprof_us[PPROF_SLOTS], pprof_last;
+static uint32_t pprof_n[PPROF_SLOTS];
+static void pprof_add(uint32_t op, int64_t us)
+{
+    unsigned i = op < 0x100 ? op : op >= QEGL_OP_GL_BASE && op - QEGL_OP_GL_BASE < 700 ?
+                 0x100 + op - QEGL_OP_GL_BASE : 0;
+    pprof_us[i] += us;
+    pprof_n[i]++;
+}
+
+static void pprof_report(QEGLState *s)
+{
+    int64_t now = g_get_monotonic_time(), total = 0;
+    int k, i;
+    if (!pprof_last) {
+        pprof_last = now;
+        return;
+    }
+    if (now - pprof_last < (int64_t)s->prof * 1000000) {
+        return;
+    }
+    for (i = 0; i < PPROF_SLOTS; i++) {
+        total += pprof_us[i];
+    }
+    fprintf(stderr, "qegl: prof %.1f s: commands %.1f ms total, make-current %u x %.3f ms\n",
+            (now - pprof_last) / 1e6, total / 1e3, prof_mc_n,
+            prof_mc_n ? prof_mc_us / 1e3 / prof_mc_n : 0.0);
+    for (k = 0; k < 14; k++) {
+        int best = -1;
+        for (i = 0; i < PPROF_SLOTS; i++) {
+            if (pprof_n[i] && (best < 0 || pprof_us[i] > pprof_us[best])) {
+                best = i;
+            }
+        }
+        if (best < 0 || !pprof_us[best]) {
+            break;
+        }
+        fprintf(stderr, "qegl: prof   %-24s %3d %6u x %8.3f ms = %8.1f ms\n",
+                best >= 0x100 ? qegl_gl_name(best - 0x100) : "control",
+                best >= 0x100 ? best - 0x100 : best,
+                pprof_n[best], pprof_us[best] / 1e3 / pprof_n[best], pprof_us[best] / 1e3);
+        pprof_us[best] = 0;
+        pprof_n[best] = 0;
+    }
+    memset(pprof_us, 0, sizeof(pprof_us));
+    memset(pprof_n, 0, sizeof(pprof_n));
+    prof_mc_us = 0;
+    prof_mc_n = 0;
+    pprof_last = now;
+}
+
+static void process_ring(QEGLState *s, int ri, bool limited, uint32_t limit)
+{
+    struct qegl_ring_desc *rd = ring_desc(s, ri);
+    struct QEGLRingState *rs = &s->rings[ri];
+    uint64_t gpa = ((uint64_t)rd->gpa_hi << 32) | rd->gpa_lo;
+    uint32_t size = rd->size;
+    uint32_t head = rd->head, tail;
+    QEGLExec e;
+    int err;
+
+    if (!rd->gpa_lo || size < 4096 || size > (64u << 20) || (size & (size - 1))) {
+        rd->error = QEGL_ERR_RANGE;
+        return;
+    }
+    if (!rs->map || rs->map_gpa != gpa || rs->map_len != size) {
+        hwaddr len = size;
+        if (rs->map) {
+            address_space_unmap(&address_space_memory, rs->map, rs->map_len, true,
+                                rs->map_len);
+            rs->map = NULL;
+        }
+        rs->map = address_space_map(&address_space_memory, gpa, &len, true,
+                                    MEMTXATTRS_UNSPECIFIED);
+        if (!rs->map || len != size) {
+            if (rs->map) {
+                address_space_unmap(&address_space_memory, rs->map, len, false, 0);
+            }
+            rs->map = NULL;
+            error_report("qegl: ring %d: cannot map guest RAM 0x%" PRIx64, ri, gpa);
+            rd->error = QEGL_ERR_RANGE;
+            return;
+        }
+        rs->map_gpa = gpa;
+        rs->map_len = size;
+    }
+    e.s = s;
+    e.ring = ri;
+    e.ctx = find_ctx(s, s->rings[ri].cur_ctx);
+    e.shm = s->shm;
+    e.shm_size = s->shm_size;
+    e.rbuf = rs->map;
+    e.rbuf_size = size;
+    make_current(s, e.ctx ? e.ctx->hglrc : s->master);
+    rs->active = true;
+    rd->busy = 1;
+    smp_mb();
+    int64_t t_start = g_get_monotonic_time();
+    tail = qatomic_load_acquire(&rd->tail);
+    while (head != tail) {
+        uint32_t pos = head & (size - 1);
+        if (limited && (int32_t)(head - limit) >= 0 && !(e.ctx && e.ctx->in_begin)) {
+            break;      /* a ring barrier's count reached (never inside glBegin/glEnd) */
+        }
+        const struct qegl_cmd *cmd = (const struct qegl_cmd *)(rs->map + pos);
+        uint32_t op = cmd->op, len = cmd->len;
+        uint32_t avail = tail - head;
+
+        if (len < 8 || (len & 7) || len > avail || pos + len > size) {
+            error_report("qegl: ring %d: bad command framing (op 0x%x len %u at %u)", ri, op,
+                         len, head);
+            rd->error = QEGL_ERR_BAD_CMD;
+            head = tail;        /* drop the rest; the guest will notice the error */
+            break;
+        }
+        err = 0;
+        rec_write(QEGL_REC_CMD, ri, pos, size, cmd, len);
+        qegl_dbg_ring = ri;
+        qegl_dbg_op = op;
+        qegl_dbg_len = len;
+        qegl_dbg_args = (const uint8_t *)(cmd + 1);
+        int64_t t_cmd = s->prof ? g_get_monotonic_time() : 0;
+        if (qegl_debug >= 3 && ri >= 2) {       /* full command trace of application rings */
+            const uint32_t *aa = (const uint32_t *)(cmd + 1);
+            fprintf(stderr, "T %d %s %x %x %x %x %x %x\n", ri,
+                    op >= QEGL_OP_GL_BASE ? qegl_gl_name(op - QEGL_OP_GL_BASE) : "ctl",
+                    op < QEGL_OP_GL_BASE ? op : (len > 8 ? aa[0] : 0), len > 12 ? aa[1] : 0,
+                    len > 16 ? aa[2] : 0, len > 20 ? aa[3] : 0, len > 24 ? aa[4] : 0,
+                    len > 28 ? aa[5] : 0);
+        }
+        if (qegl_prof < 0) {
+            qegl_prof = getenv("QEGL_PROF") != NULL;
+            qegl_fbocheck = getenv("QEGL_FBOCHECK") != NULL;
+        }
+        if (qegl_debug >= 2) {
+            diag_before(s, &e, ri, op, cmd, len);
+        }
+        int64_t pt0 = qegl_prof ? prof_now() : 0;
+        if (op == QEGL_OP_PAD) {
+            /* nothing */
+        } else if (op == QEGL_OP_FENCE) {
+            if (len >= 12) {
+                if (e.ctx && s->current == e.ctx->hglrc) {
+                    GLenum ge = glGetError();
+                    if (ge != GL_NO_ERROR && !rd->gl_error) {
+                        rd->gl_error = ge;
+                    }
+                }
+                qatomic_store_release(&rd->fence_done, ((const uint32_t *)(cmd + 1))[0]);
+            }
+        } else if (op >= QEGL_OP_GL_BASE) {
+            if (!e.ctx || !e.ctx->id) {
+                err = QEGL_ERR_BAD_OBJECT;      /* no context, or one destroyed meanwhile */
+            } else {
+                if (s->current != e.ctx->hglrc) {
+                    make_current(s, e.ctx->hglrc);
+                }
+                err = qegl_exec_gl(&e, op - QEGL_OP_GL_BASE, (const uint8_t *)(cmd + 1),
+                                   len - 8);
+                if (op == QEGL_OP_GL_BASE + QI_begin) {
+                    e.ctx->in_begin = true;
+                } else if (op == QEGL_OP_GL_BASE + QI_end) {
+                    e.ctx->in_begin = false;
+                }
+                s->current_in_begin = e.ctx->in_begin;
+            }
+        } else {
+            err = exec_control(&e, op, (const uint32_t *)(cmd + 1), (len - 8) / 4);
+        }
+        if (qegl_prof) {
+            prof_add(op, pt0);
+            prof_report();
+        }
+        if (qegl_debug >= 2 || qegl_fbocheck) {
+            diag_after(s, &e, ri, op, cmd, len);
+        }
+        if (err) {
+            if (qegl_debug) {
+                qlog("qegl: ring %d op 0x%x len %u error %d\n", ri, op, len, err);
+            }
+            if (!rd->error) {
+                rd->error = err;
+            }
+        }
+        if (s->prof) {
+            pprof_add(op, g_get_monotonic_time() - t_cmd);
+        }
+        if (s->heapcheck > 1 || (s->heapcheck && ri >= 2)) {
+            heap_check(ri, op, len, (const uint32_t *)(cmd + 1));
+        }
+        head += len;
+        qatomic_store_release(&rd->head, head);
+        if (head == tail) {
+            tail = qatomic_load_acquire(&rd->tail);
+        }
+        /* The guest kicked in the middle of glBegin/glEnd: wait for the rest instead of
+           switching to another ring's context (the NVIDIA driver does not survive context
+           switches inside Begin/End: corrupted state and later crashes). */
+        if (head == tail && e.ctx && e.ctx->in_begin) {
+            int64_t t0 = g_get_monotonic_time();
+            while ((tail = qatomic_load_acquire(&rd->tail)) == head && !s->quit &&
+                   g_get_monotonic_time() - t0 < 200000) {
+                g_usleep(10);
+            }
+            if (tail == head && qegl_debug) {
+                qlog("qegl: ring %d stalled inside glBegin/glEnd\n", ri);
+            }
+        }
+    }
+    if (e.ctx && s->current == e.ctx->hglrc) {
+        GLenum ge = glGetError();
+        if (ge != GL_NO_ERROR && !rd->gl_error) {
+            rd->gl_error = ge;
+        }
+    }
+    qatomic_store_release(&rd->head, head);
+    if (qegl_rec) {
+        fflush(qegl_rec);
+    }
+    rd->busy = 0;
+    rs->active = false;
+    s->st_ring_us += g_get_monotonic_time() - t_start;
+    if (s->prof) {
+        pprof_report(s);
+    }
+}
+
+/* Mesa backends: load <mesa dir>\opengl32.dll by full path - QEMU already has the system
+   opengl32 (libgdk-3 imports it), and epoxy always resolves through that one, so every GL/WGL
+   call of qegl then goes through qegl_glfn[] (qegl-glfn.h). GALLIUM_DRIVER picks the gallium
+   driver: zink (GL on Vulkan), d3d12 (GL on Direct3D 12), llvmpipe/softpipe (CPU). */
+static bool mesa_load(QEGLState *s)
+{
+    char dir[MAX_PATH], path[MAX_PATH + 32];
+    int missing;
+
+    if (s->mesa_dir && s->mesa_dir[0]) {
+        char *p;
+        snprintf(dir, sizeof(dir), "%s", s->mesa_dir);
+        for (p = dir; *p; p++) {
+            if (*p == '/') {
+                *p = '\\';      /* LoadLibraryEx wants backslashes */
+            }
+        }
+    } else {
+        char *sl;
+        GetModuleFileNameA(NULL, dir, sizeof(dir) - 8);
+        sl = strrchr(dir, '\\');
+        strcpy(sl ? sl + 1 : dir, "mesa");
+    }
+    _putenv_s("GALLIUM_DRIVER", s->backend);
+    SetEnvironmentVariableA("GALLIUM_DRIVER", s->backend);
+    snprintf(path, sizeof(path), "%s\\opengl32.dll", dir);
+    qegl_mesa_module = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+    if (!qegl_mesa_module) {
+        error_report("qegl: backend %s: cannot load %s (%lu)", s->backend, path,
+                     GetLastError());
+        return false;
+    }
+    missing = qegl_glfn_resolve(false);
+    qegl_glfn_on = true;
+    info_report("qegl: backend %s: Mesa from %s (%d entry points left for "
+                "wglGetProcAddress)", s->backend, path, missing);
+    return true;
+}
+
+/* what the guest sees as the host's GL_RENDERER (it appends " (QEGL)"). Core Image picks its
+   GPU code path by the prefix ("NVIDIA GeForce ", "ATI Radeon ", ...) and stays on the CPU
+   for names it does not know, so Mesa's "zink Vulkan 1.4(NVIDIA GeForce ... (NVIDIA_...))" /
+   "D3D12 (NVIDIA GeForce ...)" become "NVIDIA GeForce ... (zink)". */
+static void set_renderer_name(QEGLState *s, char *out, size_t size, const char *host)
+{
+    const char *o, *c;
+    if (s->renderer && s->renderer[0]) {
+        char *p;
+        snprintf(out, size, "%s", s->renderer);
+        for (p = out; *p; p++) {
+            if (*p == '_') {
+                *p = ' ';
+            }
+        }
+        return;
+    }
+    snprintf(out, size, "%s", host);
+    if (!s->mesa || !(o = strchr(host, '(')) || !(c = strrchr(host, ')')) || c <= o + 1) {
+        return;
+    }
+    {
+        char gpu[128];
+        char *q;
+        snprintf(gpu, sizeof(gpu), "%.*s", (int)(c - o - 1), o + 1);
+        q = strstr(gpu, " (");          /* zink: " (NVIDIA_PROPRIETARY)" */
+        if (q) {
+            *q = 0;
+        }
+        if (!strncmp(gpu, "LLVM", 4)) {
+            return;                     /* llvmpipe: no GPU name to borrow */
+        }
+        snprintf(out, size, "%s (%s)", gpu, s->backend);
+    }
+}
+
+static bool gl_init(QEGLState *s)
+{
+    WNDCLASSA wc = { 0 };
+    PIXELFORMATDESCRIPTOR pfd = { 0 };
+    struct qegl_shm_header *hdr = (struct qegl_shm_header *)s->shm;
+    int pf;
+    GLint v;
+
+    if (s->mesa && !mesa_load(s)) {
+        return false;
+    }
+
+    wc.lpfnWndProc = DefWindowProcA;
+    wc.hInstance = GetModuleHandleA(NULL);
+    wc.lpszClassName = "qegl-render";
+    RegisterClassA(&wc);
+    s->hwnd = CreateWindowA("qegl-render", "qegl", WS_OVERLAPPEDWINDOW, 0, 0, 16, 16,
+                            NULL, NULL, wc.hInstance, NULL);
+    if (!s->hwnd) {
+        return false;
+    }
+    s->hdc = GetDC(s->hwnd);
+    pfd.nSize = sizeof(pfd);
+    pfd.nVersion = 1;
+    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+    pfd.iPixelType = PFD_TYPE_RGBA;
+    pfd.cColorBits = 32;
+    pfd.cAlphaBits = 8;
+    pfd.cDepthBits = 24;
+    pfd.cStencilBits = 8;
+    if (s->mesa) {
+        /* Mesa's own pixel formats (GDI's SetPixelFormat would go to the system opengl32) */
+        int (WINAPI *choose)(HDC, const PIXELFORMATDESCRIPTOR *) =
+            (void *)GetProcAddress(qegl_mesa_module, "wglChoosePixelFormat");
+        BOOL (WINAPI *set)(HDC, int, const PIXELFORMATDESCRIPTOR *) =
+            (void *)GetProcAddress(qegl_mesa_module, "wglSetPixelFormat");
+        pf = choose ? choose(s->hdc, &pfd) : 0;
+        if (!pf || !set || !set(s->hdc, pf, &pfd)) {
+            error_report("qegl: backend %s: no pixel format (%lu)", s->backend,
+                         GetLastError());
+            return false;
+        }
+    } else {
+        pf = ChoosePixelFormat(s->hdc, &pfd);
+        if (!pf || !SetPixelFormat(s->hdc, pf, &pfd)) {
+            return false;
+        }
+    }
+    s->master = wglCreateContext(s->hdc);
+    if (!s->master) {
+        return false;
+    }
+    make_current(s, s->master);
+    if (s->mesa) {
+        int missing = qegl_glfn_resolve(true);
+        if (missing) {
+            warn_report("qegl: backend %s lacks %d of %d GL entry points qegl uses",
+                        s->backend, missing, QEGL_GLFN_COUNT);
+        }
+    }
+    s->rb_pbo = s->pbo < 0 ? !s->mesa : s->pbo != 0;
+    snprintf(hdr->gl_vendor, sizeof(hdr->gl_vendor), "%s", glGetString(GL_VENDOR));
+    set_renderer_name(s, hdr->gl_renderer, sizeof(hdr->gl_renderer),
+                      (const char *)glGetString(GL_RENDERER));
+    snprintf(hdr->gl_version, sizeof(hdr->gl_version), "%s", glGetString(GL_VERSION));
+    if (s->mesa) {
+        info_report("qegl: backend %s: GL_RENDERER %s, reported to the guest as \"%s\"",
+                    s->backend, glGetString(GL_RENDERER), hdr->gl_renderer);
+    }
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &v);
+    hdr->max_texture_size = v;
+    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &v);
+    hdr->max_texture_units = v;
+    qegl_gl_init();
+    qegl_gl3_init();
+    if (s->apps && s->gl3 && qegl_gl3_ok) {
+        hdr->features |= QEGL_FEAT_GL3;
+    }
+    info_report("qegl: host GL %s / %s / %s", hdr->gl_vendor, hdr->gl_renderer,
+                hdr->gl_version);
+    return true;
+}
+
+static void init_shm_header(QEGLState *s)
+{
+    struct qegl_shm_header *hdr = (struct qegl_shm_header *)s->shm;
+    struct qegl_shm_header keep = *hdr;
+    memset(s->shm, 0, 0x10000);
+    hdr->magic = QEGL_MAGIC;
+    hdr->version = QEGL_VERSION;
+    hdr->shm_size = s->shm_size;
+    hdr->max_rings = QEGL_MAX_RINGS;
+    hdr->features = (s->apps ? QEGL_FEAT_APP_GL : 0) | QEGL_FEAT_RING_BARRIER |
+                    (s->apps && s->gl3 && qegl_gl3_ok ? QEGL_FEAT_GL3 : 0);
+    hdr->max_texture_size = keep.max_texture_size;
+    hdr->max_texture_units = keep.max_texture_units;
+    memcpy(hdr->gl_vendor, keep.gl_vendor, sizeof(keep.gl_vendor));
+    memcpy(hdr->gl_renderer, keep.gl_renderer, sizeof(keep.gl_renderer));
+    memcpy(hdr->gl_version, keep.gl_version, sizeof(keep.gl_version));
+}
+
+/* replay filters (environment), for bisecting: QEGL_REPLAY_SKIP_RINGS=<bitmask>,
+   QEGL_REPLAY_SKIP_OPS=<op[@from-to],...> (hex op, optionally only within that range of
+   commands, e.g. 1f,103b@159429-805754), QEGL_REPLAY_SKIP_RANGE=<from-to,...>,
+   QEGL_REPLAY_SKIP_RESETS=1, QEGL_REPLAY_MAX=<commands> */
+
+static bool replay_skip_op(uint32_t op, uint64_t ncmd)
+{
+    /* entries: op[@a-b] (hex op, decimal command range) */
+    static uint32_t ops[64];
+    static uint64_t lo[64], hi[64];
+    static int n = -1;
+    int i;
+    if (n < 0) {
+        const char *e = getenv("QEGL_REPLAY_SKIP_OPS");
+        n = 0;
+        while (e && *e && n < 64) {
+            char *end;
+            ops[n] = strtoul(e, &end, 16);
+            lo[n] = 0;
+            hi[n] = UINT64_MAX;
+            if (*end == '@') {
+                lo[n] = strtoull(end + 1, &end, 10);
+                hi[n] = *end == '-' ? strtoull(end + 1, &end, 10) : lo[n];
+            }
+            n++;
+            e = *end ? end + 1 : end;
+        }
+    }
+    for (i = 0; i < n; i++) {
+        if (ops[i] == op && ncmd >= lo[i] && ncmd <= hi[i]) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static void replay_stream(QEGLState *s)
+{
+    uint32_t loop;
+    uint64_t skip_rings = getenv("QEGL_REPLAY_SKIP_RINGS") ?
+                          strtoull(getenv("QEGL_REPLAY_SKIP_RINGS"), NULL, 0) : 0;
+    bool skip_resets = getenv("QEGL_REPLAY_SKIP_RESETS") != NULL;
+    uint64_t max = getenv("QEGL_REPLAY_MAX") ? strtoull(getenv("QEGL_REPLAY_MAX"), NULL, 0) : 0;
+    /* QEGL_REPLAY_SKIP_RANGE=a-b[,c-d...]: skip commands a..b (1-based) */
+    uint64_t rng[16][2];
+    int nrng = 0;
+    {
+        const char *r = getenv("QEGL_REPLAY_SKIP_RANGE");
+        while (r && *r && nrng < 16) {
+            char *end;
+            rng[nrng][0] = strtoull(r, &end, 10);
+            rng[nrng][1] = *end == '-' ? strtoull(end + 1, &end, 10) : rng[nrng][0];
+            nrng++;
+            r = *end ? end + 1 : end;
+        }
+    }
+    for (loop = 0; loop < MAX(s->replay_loops, 1); loop++) {
+        FILE *f = fopen(s->replay, "rb");
+        uint32_t h[5];
+        uint64_t ncmd = 0;
+        int64_t t0 = g_get_monotonic_time();
+        uint8_t *buf = NULL;
+        if (!f) {
+            error_report("qegl: replay: cannot open %s", s->replay);
+            return;
+        }
+        while (fread(h, sizeof(h), 1, f) == 1) {
+            uint32_t ri = h[1], pos = h[2], size = h[3], len = h[4];
+            if (h[0] == QEGL_REC_RESET) {
+                if (!skip_resets) {
+                    reset_ring_objects(s, ri == 0xffffffffu ? -1 : (int)ri);
+                }
+                continue;
+            }
+            if (max && replay_ncmd >= max) {
+                break;
+            }
+            if (h[0] != QEGL_REC_CMD || ri >= QEGL_MAX_RINGS || len < 8 || !size ||
+                pos + len > size) {
+                error_report("qegl: replay: bad record after %" PRIu64 " commands", ncmd);
+                break;
+            }
+            struct QEGLRingState *rs = &s->rings[ri];
+            if (!rs->map || rs->map_len != size) {
+                if (rs->map && rs->owned) {
+                    g_free(rs->map);
+                }
+                rs->map = g_malloc0(size);
+                rs->map_len = size;
+                rs->owned = true;
+            }
+            buf = rs->map + pos;
+            if (fread(buf, len, 1, f) != 1) {
+                break;
+            }
+            replay_ncmd++;
+            ncmd++;
+            bool in_range = false;
+            for (int k = 0; k < nrng; k++) {
+                in_range |= replay_ncmd >= rng[k][0] && replay_ncmd <= rng[k][1];
+            }
+            if (in_range || (skip_rings >> ri) & 1 ||
+                replay_skip_op(((const struct qegl_cmd *)buf)->op, replay_ncmd)) {
+                /* still track the ring's current context */
+                const struct qegl_cmd *c2 = (const struct qegl_cmd *)buf;
+                if (c2->op == QEGL_OP_MAKE_CURRENT && len >= 12) {
+                    /* skipped: keep cur_ctx as the recording had it */
+                    rs->cur_ctx = ((const uint32_t *)(c2 + 1))[0];
+                }
+                continue;
+            }
+            {
+                const struct qegl_cmd *cmd = (const struct qegl_cmd *)buf;
+                uint32_t op = cmd->op;
+                QEGLExec e;
+                e.s = s;
+                e.ring = ri;
+                e.ctx = find_ctx(s, rs->cur_ctx);
+                e.shm = s->shm;
+                e.shm_size = s->shm_size;
+                e.rbuf = rs->map;
+                e.rbuf_size = size;
+                qegl_dbg_ring = ri;
+                qegl_dbg_op = op;
+                qegl_dbg_len = len;
+                qegl_dbg_args = (const uint8_t *)(cmd + 1);
+                if (qegl_prof < 0) {
+                    qegl_prof = getenv("QEGL_PROF") != NULL;
+                    qegl_fbocheck = getenv("QEGL_FBOCHECK") != NULL;
+                }
+                if (qegl_debug >= 2 && e.ctx && s->current == e.ctx->hglrc) {
+                    diag_before(s, &e, ri, op, cmd, len);
+                }
+                if (op == QEGL_OP_PAD || op == QEGL_OP_FENCE) {
+                    /* nothing */
+                } else if (op >= QEGL_OP_GL_BASE) {
+                    if (e.ctx) {
+                        make_current(s, e.ctx->hglrc);
+                        qegl_exec_gl(&e, op - QEGL_OP_GL_BASE, (const uint8_t *)(cmd + 1),
+                                     len - 8);
+                        if (op == QEGL_OP_GL_BASE + QI_begin) {
+                            e.ctx->in_begin = true;
+                        } else if (op == QEGL_OP_GL_BASE + QI_end) {
+                            e.ctx->in_begin = false;
+                        }
+                        s->current_in_begin = e.ctx->in_begin;
+                    }
+                } else {
+                    if (e.ctx) {
+                        make_current(s, e.ctx->hglrc);
+                    }
+                    exec_control(&e, op, (const uint32_t *)(cmd + 1), (len - 8) / 4);
+                }
+                if (qegl_debug >= 2 || qegl_fbocheck) {
+                    diag_after(s, &e, ri, op, cmd, len);
+                }
+            }
+        }
+        fclose(f);
+        reset_ring_objects(s, -1);
+        fprintf(stderr, "qegl: replay loop %u: %" PRIu64 " commands in %.1f s\n", loop + 1, ncmd,
+                (g_get_monotonic_time() - t0) / 1e6);
+    }
+}
+
+static void *render_thread(void *opaque)
+{
+    QEGLState *s = opaque;
+
+    qegl_render_tid = GetCurrentThreadId();
+    /* The render thread runs short bursts that the guest often waits for (syncs, presents).
+       With vCPU threads busy - an idle Tiger guest keeps all its vCPUs running under WHPX -
+       a normal-priority render thread got a fraction of a core: application GL ran 5-20x
+       slower whenever a few VMs ran. */
+    if (getenv("QEGL_RENDER_PRIO")) {      /* e.g. 0 = normal, for comparisons */
+        qegl_render_prio = atoi(getenv("QEGL_RENDER_PRIO"));
+    }
+    SetThreadPriority(GetCurrentThread(), qegl_render_prio);
+    rcu_register_thread();
+    if (gl_init(s)) {
+        raise_driver_threads();
+        s->status = QEGL_STATUS_GL_READY;
+        if (s->replay) {
+            replay_stream(s);
+            while (qlog_q && g_async_queue_length(qlog_q) > 0) {
+                g_usleep(1000);     /* let the logger write what is queued */
+            }
+            fprintf(stderr, "qegl: replay finished\n");
+            fflush(stderr);
+            _exit(0);
+        }
+        rec_open();
+    } else {
+        error_report("qegl: host OpenGL initialisation failed (%lu)", GetLastError());
+        s->status = QEGL_STATUS_GL_FAILED;
+    }
+    for (;;) {
+        uint64_t resets;
+        bool reset_all, sync_req;
+        uint32_t sync_gen;
+        int ring = -1, i;
+
+        qemu_mutex_lock(&s->lock);
+        while (!s->qcount && !s->reset_rings && !s->reset_all && !s->quit &&
+               !s->so_sync_req) {
+            /* zero-copy scanout: frames to hand over, VRAM to bring up to date */
+            int due = s->status == QEGL_STATUS_GL_READY ? so_idle_due_ms(s) : -1;
+            if (due == 0) {
+                break;
+            } else if (due < 0) {
+                qemu_cond_wait(&s->cond, &s->lock);
+            } else {
+                qemu_cond_timedwait(&s->cond, &s->lock, due);
+            }
+        }
+        if (s->quit) {
+            qemu_mutex_unlock(&s->lock);
+            break;
+        }
+        sync_req = s->so_sync_req;
+        sync_gen = s->so_sync_gen;
+        s->so_sync_req = false;
+        resets = s->reset_rings;
+        reset_all = s->reset_all;
+        s->reset_rings = 0;
+        s->reset_all = false;
+        if (reset_all) {
+            s->qcount = 0;
+            s->queued = 0;
+        } else if (s->qcount) {
+            ring = s->queue[s->qhead];
+            s->qhead = (s->qhead + 1) % QEGL_MAX_RINGS;
+            s->qcount--;
+            s->queued &= ~(1ull << ring);
+        }
+        qemu_mutex_unlock(&s->lock);
+
+        if (s->status != QEGL_STATUS_GL_READY) {
+            if (sync_req) {
+                qemu_mutex_lock(&s->lock);
+                s->so_sync_done = sync_gen;
+                qemu_cond_broadcast(&s->so_sync_cond);
+                qemu_mutex_unlock(&s->lock);
+            }
+            continue;
+        }
+        if ((ring < 0 && !reset_all && !resets) || sync_req) {
+            /* sync_req: screendump, the console surface must show the newest frame */
+            so_idle(s, sync_req);
+        }
+        if (sync_req) {
+            qemu_mutex_lock(&s->lock);
+            s->so_sync_done = sync_gen;
+            qemu_cond_broadcast(&s->so_sync_cond);
+            qemu_mutex_unlock(&s->lock);
+        }
+        if (reset_all) {
+            rec_write(QEGL_REC_RESET, 0xffffffffu, 0, 0, NULL, 0);
+            reset_ring_objects(s, -1);
+            init_shm_header(s);
+        }
+        for (i = 0; i < QEGL_MAX_RINGS; i++) {
+            if (resets & (1ull << i)) {
+                rec_write(QEGL_REC_RESET, i, 0, 0, NULL, 0);
+                reset_ring_objects(s, i);
+            }
+        }
+        if (qegl_rec && (reset_all || resets)) {
+            fflush(qegl_rec);
+        }
+        if (ring >= 0) {
+            process_ring(s, ring, false, 0);
+            if (s->so_npend) {
+                so_poll(s, 0);      /* zero-copy (Mesa): hand finished frames over now */
+            }
+        }
+    }
+    rcu_unregister_thread();
+    return NULL;
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* device */
+
+static uint64_t qegl_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+    QEGLState *s = opaque;
+    switch (addr) {
+    case QEGL_REG_MAGIC:
+        return QEGL_MAGIC;
+    case QEGL_REG_VERSION:
+        return QEGL_VERSION;
+    case QEGL_REG_SHM_SIZE:
+        return s->shm_size;
+    case QEGL_REG_STATUS:
+        return s->status;
+    case QEGL_REG_CURSOR_CAPS:
+        return s->cursor ? QEGL_CURSOR_MAX : 0;
+    case QEGL_REG_CURSOR_SIZE:
+        return s->cursor_size;
+    case QEGL_REG_CURSOR_HOT:
+        return s->cursor_hot;
+    case QEGL_REG_HOST_REFRESH:
+        return qatomic_read(&s->host_refresh_mhz);
+    case QEGL_REG_VBL_RATE:
+        return qatomic_read(&s->vbl_mhz);
+    }
+    return 0;
+}
+
+/* vCPU thread: copy the image out of BAR2 now (the guest may overwrite it right after) */
+static void qegl_cursor_define(QEGLState *s)
+{
+    uint32_t w = s->cursor_size & 0xffff, h = s->cursor_size >> 16;
+    QEMUCursor *c;
+
+    if (!s->cursor || !w || !h || w > QEGL_CURSOR_MAX || h > QEGL_CURSOR_MAX) {
+        return;
+    }
+    c = cursor_alloc(w, h);
+    if (!c) {
+        return;
+    }
+    memcpy(c->data, s->shm + QEGL_CURSOR_OFF, w * h * 4);
+    c->hot_x = MIN(s->cursor_hot & 0xffff, w - 1);
+    c->hot_y = MIN(s->cursor_hot >> 16, h - 1);
+    if (qegl_debug) {
+        static int logged;
+        if (logged < 40) {
+            logged++;
+            qlog("qegl: cursor %ux%u hot %d,%d\n", w, h, c->hot_x, c->hot_y);
+        }
+    }
+    qemu_mutex_lock(&s->cursor_lock);
+    if (s->cursor_new) {
+        cursor_unref(s->cursor_new);
+    }
+    s->cursor_new = c;
+    s->cursor_hx = c->hot_x;
+    s->cursor_hy = c->hot_y;
+    s->cursor_dirty = true;
+    s->st_cursor_defs++;
+    qemu_mutex_unlock(&s->cursor_lock);
+    qemu_bh_schedule(s->cursor_bh);
+}
+
+static void qegl_cursor_state(QEGLState *s, int x, int y, int visible)
+{
+    qemu_mutex_lock(&s->cursor_lock);
+    if (visible >= 0) {
+        if (qegl_debug && visible != s->cursor_visible) {
+            static int logged;
+            if (logged++ < 100) {
+                qlog("qegl: cursor %s\n", visible ? "shown" : "hidden");
+            }
+        }
+        s->cursor_visible = visible;
+    } else {
+        s->cursor_x = x;
+        s->cursor_y = y;
+        s->st_cursor_moves++;
+    }
+    s->cursor_dirty = true;
+    qemu_mutex_unlock(&s->cursor_lock);
+    qemu_bh_schedule(s->cursor_bh);
+}
+
+/* main loop */
+static void qegl_cursor_bh(void *opaque)
+{
+    QEGLState *s = opaque;
+    QemuConsole *con = qemu_console_lookup_by_index(0);
+    QEMUCursor *c;
+    int x, y;
+    bool visible;
+
+    qemu_mutex_lock(&s->cursor_lock);
+    c = s->cursor_new;
+    s->cursor_new = NULL;
+    x = s->cursor_x + s->cursor_hx;
+    y = s->cursor_y + s->cursor_hy;
+    visible = s->cursor_visible;
+    s->cursor_dirty = false;
+    qemu_mutex_unlock(&s->cursor_lock);
+
+    if (con && c) {
+        qemu_console_set_cursor(con, c);
+    }
+    if (c) {
+        cursor_unref(c);
+    }
+    if (con) {
+        qemu_console_set_mouse(con, MAX(x, 0), MAX(y, 0), visible);
+    }
+}
+
+static void qegl_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    QEGLState *s = opaque;
+    switch (addr) {
+    case QEGL_REG_DOORBELL:
+        if (val < QEGL_MAX_RINGS) {
+            qemu_mutex_lock(&s->lock);
+            if (!(s->queued & (1ull << val))) {
+                s->queue[(s->qhead + s->qcount) % QEGL_MAX_RINGS] = val;
+                s->qcount++;
+                s->queued |= 1ull << val;
+                qemu_cond_signal(&s->cond);
+            }
+            qemu_mutex_unlock(&s->lock);
+        }
+        break;
+    case QEGL_REG_RING_RESET:
+        if (val < QEGL_MAX_RINGS) {
+            qemu_mutex_lock(&s->lock);
+            s->reset_rings |= 1ull << val;
+            qemu_cond_signal(&s->cond);
+            qemu_mutex_unlock(&s->lock);
+        }
+        break;
+    case QEGL_REG_CURSOR_SIZE:
+        s->cursor_size = val;
+        break;
+    case QEGL_REG_CURSOR_HOT:
+        s->cursor_hot = val;
+        break;
+    case QEGL_REG_CURSOR_DEFINE:
+        qegl_cursor_define(s);
+        break;
+    case QEGL_REG_CURSOR_POS:
+        if (s->cursor) {
+            qegl_cursor_state(s, (int16_t) (val & 0xffff), (int16_t) (val >> 16), -1);
+        }
+        break;
+    case QEGL_REG_CURSOR_VISIBLE:
+        if (s->cursor) {
+            qegl_cursor_state(s, 0, 0, val != 0);
+        }
+        break;
+    }
+}
+
+static const MemoryRegionOps qegl_mmio_ops = {
+    .read = qegl_mmio_read,
+    .write = qegl_mmio_write,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* main loop: let the display (the VGA console) pick up a freshly presented frame right away
+   instead of waiting for the next GUI refresh tick (30 ms in QEMU's SDL backend) */
+
+/* main loop: does the display of console 0 take frames as shared D3D11 textures? */
+static void qegl_surface_sync(void *opaque);
+
+static void qegl_scanout_check(QEGLState *s)
+{
+    QemuConsole *con = qemu_console_lookup_by_index(0);
+    QemuD3DScanout *sc = NULL;
+
+    if (!con || !s->scanout) {
+        return;
+    }
+    if (s->so_con != con) {
+        qemu_console_set_surface_sync(con, qegl_surface_sync, s);
+        s->so_con = con;
+    }
+    sc = qemu_console_get_d3d_scanout(con);
+    if (qatomic_read(&s->so_avail) != sc) {
+        info_report("qegl: display %s zero-copy frames", sc ? "takes" : "does not take");
+        qatomic_set(&s->so_avail, sc);
+    }
+}
+
+/* main loop (screendump): VRAM must show the newest frame */
+static void qegl_surface_sync(void *opaque)
+{
+    QEGLState *s = opaque;
+    int64_t end = g_get_monotonic_time() + 1000000;
+    uint32_t gen;
+
+    if (!qatomic_read(&s->so_active_pub)) {
+        return;
+    }
+    qemu_mutex_lock(&s->lock);
+    gen = ++s->so_sync_gen;
+    s->so_sync_req = true;
+    qemu_cond_signal(&s->cond);
+    while ((int32_t)(s->so_sync_done - gen) < 0) {
+        int64_t left = end - g_get_monotonic_time();
+        if (left <= 0) {
+            warn_report("qegl: screendump: the render thread did not update VRAM in time");
+            break;
+        }
+        qemu_cond_timedwait(&s->so_sync_cond, &s->lock, (left + 999) / 1000);
+    }
+    qemu_mutex_unlock(&s->lock);
+}
+
+static void qegl_do_update(QEGLState *s)
+{
+    QemuConsole *con = qemu_console_lookup_by_index(0);
+    int64_t t0 = g_get_monotonic_time();
+    qatomic_set(&s->update_scheduled, 0);
+    qatomic_set(&s->last_update_ns, qemu_clock_get_ns(QEMU_CLOCK_REALTIME));
+    if (con && s->display_update == 1) {
+        int64_t f = qatomic_xchg(&s->lat_frame_ns, 0);
+        qemu_console_hw_update(con);
+        if (f) {
+            int64_t us = (qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - f) / 1000;
+            qatomic_add(&s->st_lat_us, us);
+            qatomic_inc(&s->st_lat_n);
+            if (us > qatomic_read(&s->st_lat_max_us)) {
+                qatomic_set(&s->st_lat_max_us, us);
+            }
+        }
+    }
+    qegl_scanout_check(s);
+    qatomic_add(&s->st_disp_us, g_get_monotonic_time() - t0);
+    qatomic_inc(&s->st_disp_n);
+}
+
+static void qegl_update_timer(void *opaque)
+{
+    qegl_do_update(opaque);
+}
+
+static void qegl_update_bh(void *opaque)
+{
+    qegl_do_update(opaque);
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* display timing: which monitor shows the VM, and its refresh rate */
+
+static BOOL CALLBACK find_ui_window(HWND w, LPARAM lp)
+{
+    DWORD pid = 0;
+    char cls[32];
+    GetWindowThreadProcessId(w, &pid);
+    if (pid == GetCurrentProcessId() && IsWindowVisible(w) &&
+        GetClassNameA(w, cls, sizeof(cls)) &&
+        (!strcmp(cls, "SDL_app") || !strcmp(cls, "QEMU_D3D11"))) {
+        *(HWND *)lp = w;
+        return FALSE;
+    }
+    return TRUE;
+}
+
+/* refresh rate in mHz of the monitor showing our SDL window (primary monitor when headless) */
+static uint32_t host_refresh_mhz(void)
+{
+    HWND w = NULL;
+    HMONITOR mon;
+    MONITORINFOEXW mi;
+    DEVMODEW dm;
+    POINT origin = { 0, 0 };
+
+    EnumWindows(find_ui_window, (LPARAM)&w);
+    mon = w ? MonitorFromWindow(w, MONITOR_DEFAULTTOPRIMARY)
+            : MonitorFromPoint(origin, MONITOR_DEFAULTTOPRIMARY);
+    memset(&mi, 0, sizeof(mi));
+    mi.cbSize = sizeof(mi);
+    memset(&dm, 0, sizeof(dm));
+    dm.dmSize = sizeof(dm);
+    if (!mon || !GetMonitorInfoW(mon, (MONITORINFO *)&mi) ||
+        !EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) ||
+        dm.dmDisplayFrequency <= 1) {
+        return 0;
+    }
+    return dm.dmDisplayFrequency * 1000;
+}
+
+static void qegl_update_refresh(QEGLState *s)
+{
+    uint32_t host = host_refresh_mhz();
+    uint32_t vbl, max = MAX(s->vbl_max, 20) * 1000;
+
+    if (s->vbl_hz) {
+        vbl = s->vbl_hz * 1000;
+    } else if (!host) {
+        vbl = 60000;
+    } else {
+        vbl = host / ((host + max - 1) / max);
+    }
+    if (host != s->host_refresh_mhz || vbl != s->vbl_mhz) {
+        info_report("qegl: host display %u.%03u Hz -> guest VBL %u.%03u Hz",
+                    host / 1000, host % 1000, vbl / 1000, vbl % 1000);
+    }
+    qatomic_set(&s->host_refresh_mhz, host);
+    qatomic_set(&s->vbl_mhz, vbl);
+}
+
+static void qegl_refresh_timer(void *opaque)
+{
+    QEGLState *s = opaque;
+    qegl_update_refresh(s);
+    qegl_scanout_check(s);
+    timer_mod(s->refresh_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000);
+}
+
+static void qegl_realize(PCIDevice *dev, Error **errp)
+{
+    QEGLState *s = QEGL(dev);
+
+    qegl_debug = s->debug;
+    if (s->backend && strcmp(s->backend, "native")) {
+        if (strcmp(s->backend, "zink") && strcmp(s->backend, "d3d12") &&
+            strcmp(s->backend, "llvmpipe") && strcmp(s->backend, "softpipe")) {
+            error_setg(errp, "qegl: backend must be native, zink, d3d12, llvmpipe or softpipe");
+            return;
+        }
+        s->mesa = true;
+    }
+    if (s->shm_mb < 4 || s->shm_mb > 1024 || (s->shm_mb & (s->shm_mb - 1))) {
+        error_setg(errp, "qegl: shm_mb must be a power of two between 4 and 1024");
+        return;
+    }
+    s->shm_size = s->shm_mb * MiB;
+    memory_region_init_io(&s->mmio, OBJECT(s), &qegl_mmio_ops, s, "qegl-mmio", 4 * KiB);
+    /* the doorbell only touches qegl's own lock: no need to wait for the BQL (which the main
+       loop holds while it updates the display) */
+    memory_region_enable_lockless_io(&s->mmio);
+    if (!memory_region_init_ram(&s->shm_mr, OBJECT(s), "qegl-shm", s->shm_size, errp)) {
+        return;
+    }
+    s->shm = memory_region_get_ram_ptr(&s->shm_mr);
+    init_shm_header(s);
+    pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+    pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_PREFETCH,
+                     &s->shm_mr);
+
+    s->update_bh = qemu_bh_new(qegl_update_bh, s);
+    qegl_dbg_state = s;
+    AddVectoredExceptionHandler(1, qegl_fault_handler);
+    qemu_mutex_init(&s->cursor_lock);
+    s->cursor_bh = qemu_bh_new(qegl_cursor_bh, s);
+    qegl_update_refresh(s);
+    s->refresh_timer = timer_new_ms(QEMU_CLOCK_REALTIME, qegl_refresh_timer, s);
+    timer_mod(s->refresh_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000);
+    s->update_timer = timer_new_ns(QEMU_CLOCK_REALTIME, qegl_update_timer, s);
+    qemu_mutex_init(&s->lock);
+    qemu_cond_init(&s->cond);
+    qemu_cond_init(&s->so_sync_cond);
+    s->so_last = -1;
+    if (!qlog_q) {
+        qlog_q = g_async_queue_new();
+        qemu_thread_create(&qlog_thread, "qegl-log", qlog_main, NULL, QEMU_THREAD_DETACHED);
+    }
+    qemu_thread_create(&s->thread, "qegl-render", render_thread, s, QEMU_THREAD_JOINABLE);
+}
+
+static void qegl_exit(PCIDevice *dev)
+{
+    QEGLState *s = QEGL(dev);
+    qemu_mutex_lock(&s->lock);
+    s->quit = true;
+    qemu_cond_signal(&s->cond);
+    qemu_mutex_unlock(&s->lock);
+    qemu_thread_join(&s->thread);
+}
+
+static void qegl_reset(DeviceState *dev)
+{
+    QEGLState *s = QEGL(dev);
+    if (s->cursor_bh) {
+        qegl_cursor_state(s, 0, 0, 0);
+    }
+    s->cursor_size = s->cursor_hot = 0;
+    qemu_mutex_lock(&s->lock);
+    s->reset_all = true;
+    qemu_cond_signal(&s->cond);
+    qemu_mutex_unlock(&s->lock);
+}
+
+static const Property qegl_properties[] = {
+    DEFINE_PROP_UINT32("shm_mb", QEGLState, shm_mb, 4),
+    DEFINE_PROP_UINT32("debug", QEGLState, debug, 0),
+    DEFINE_PROP_UINT32("display_update", QEGLState, display_update, 1),
+    DEFINE_PROP_UINT32("cursor", QEGLState, cursor, 1),
+    DEFINE_PROP_STRING("replay", QEGLState, replay),
+    DEFINE_PROP_UINT32("replay_loops", QEGLState, replay_loops, 1),
+    DEFINE_PROP_UINT32("vbl_max", QEGLState, vbl_max, 120),
+    DEFINE_PROP_UINT32("apps", QEGLState, apps, 1),
+    DEFINE_PROP_UINT32("gl3", QEGLState, gl3, 1),
+    DEFINE_PROP_UINT32("vbl_hz", QEGLState, vbl_hz, 0),
+    DEFINE_PROP_STRING("backend", QEGLState, backend),
+    DEFINE_PROP_STRING("mesa", QEGLState, mesa_dir),
+    DEFINE_PROP_STRING("renderer", QEGLState, renderer),
+    DEFINE_PROP_INT32("pbo", QEGLState, pbo, -1),
+    DEFINE_PROP_UINT32("heapcheck", QEGLState, heapcheck, 0),
+    DEFINE_PROP_UINT32("prof", QEGLState, prof, 0),
+    DEFINE_PROP_UINT32("scanout", QEGLState, scanout, 1),
+};
+
+static void qegl_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+    k->vendor_id = QEGL_PCI_VENDOR;
+    k->device_id = QEGL_PCI_DEVICE;
+    /* not a display class: Apple's IONDRVFramebuffer would bind to it otherwise */
+    k->class_id = PCI_CLASS_PROCESSOR_CO;
+    k->revision = 1;
+    k->realize = qegl_realize;
+    k->exit = qegl_exit;
+    device_class_set_legacy_reset(dc, qegl_reset);
+    dc->desc = "OpenGL pass-through for the Mac OS X 10.4 guest (Quartz Extreme)";
+    dc->hotpluggable = false;
+    set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+    device_class_set_props(dc, qegl_properties);
+}
+
+static const TypeInfo qegl_info = {
+    .name = TYPE_QEGL,
+    .parent = TYPE_PCI_DEVICE,
+    .instance_size = sizeof(QEGLState),
+    .class_init = qegl_class_init,
+    .interfaces = (const InterfaceInfo[]) {
+        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+        { },
+    },
+};
+
+static void qegl_register_types(void)
+{
+    type_register_static(&qegl_info);
+}
+
+type_init(qegl_register_types)
diff --git a/hw/display/qegl.h b/hw/display/qegl.h
new file mode 100644
index 0000000..25b2d7c
--- /dev/null
+++ b/hw/display/qegl.h
@@ -0,0 +1,147 @@
+/*
+ * qegl - paravirtual OpenGL pass-through device for the Mac OS X 10.4 guest
+ * (Quartz Extreme project, G:\Tiger\qe). Internal header shared by qegl.c and qegl-gl.c.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ */
+#ifndef HW_DISPLAY_QEGL_H
+#define HW_DISPLAY_QEGL_H
+
+#include <windows.h>
+#include <epoxy/gl.h>
+#include <epoxy/wgl.h>
+#include "qegl-glfn.h"      /* GL/WGL calls: epoxy (native) or a private Mesa (zink, d3d12...) */
+#include "qegl_proto.h"
+
+#define QEGL_MAX_CTX    256
+#define QEGL_MAX_SURF   1024
+#define QEGL_MAX_GROUPS 256
+
+typedef struct QEGLState QEGLState;
+
+/* A guest share group = a host share group. Its root context owns helper objects used for
+   presenting and reading back surfaces of the group. */
+typedef struct QEGLGroup {
+    uint32_t id;                /* guest share group id, 0 = free */
+    int refs;                   /* contexts + surfaces */
+    HGLRC rc;
+    GLuint read_fbo, blit_fbo, blit_tex;
+    uint32_t blit_w, blit_h;
+    GLuint pbo;                 /* pixel pack buffer for readbacks (shared within the group) */
+    uint32_t rd_surf;           /* surface attached to read_fbo */
+    size_t pbo_size;
+    GHashTable *guest_fbos;     /* names of the guest's framebuffer objects: shared by the
+                                   group's contexts (NVIDIA shares FBOs within a share group,
+                                   as Apple's GL does) */
+    GHashTable *sampler_map;    /* GL3 path: guest sampler name -> host name (0 = deleted) */
+    GHashTable *sync_map;       /* GL3 path: guest sync id -> host GLsync */
+} QEGLGroup;
+
+typedef struct QEGLSurface {
+    uint32_t id;                /* guest id, 0 = free slot */
+    uint32_t w, h;              /* logical size (content sits in the bottom-left corner) */
+    uint32_t sw, sh;            /* storage size */
+    uint32_t flags;
+    QEGLGroup *group;
+    GLuint tex[2];              /* [0] back (or the only buffer), [1] front if double */
+    GLuint depth_stencil;       /* renderbuffer */
+    uint32_t gen;               /* bumped when tex[]/storage change; contexts re-attach */
+    int owner_ring;
+} QEGLSurface;
+
+typedef struct QEGLContext {
+    uint32_t id;                /* guest id, 0 = free slot */
+    QEGLGroup *group;
+    HGLRC hglrc;
+    int owner_ring;
+    uint32_t draw_surf, read_surf;
+    GLuint surf_fbo;            /* fbo wrapping the draw surface, and */
+    GLuint rd_fbo;              /* fbo for reading a surface: names unique to this context, since
+                                   the NVIDIA driver shares framebuffer objects between the
+                                   contexts of a share group (GL says they are per context) */
+    uint32_t fbo_surf, fbo_gen;
+    uint32_t rd_surf;           /* surface attached to this context's read fbo
+                                   (rd_fbo: readbacks, CGLTexImagePBuffer) */
+    GLuint guest_fbo;           /* fbo the guest bound with glBindFramebufferEXT (0 = surface);
+                                   with the GL3 path's split bindings: the draw framebuffer */
+    GLuint guest_read_fbo;      /* the read framebuffer (== guest_fbo unless the GL3 path bound
+                                   GL_READ_FRAMEBUFFER / GL_DRAW_FRAMEBUFFER separately) */
+    GHashTable *vao_map;        /* GL3 path: guest vertex array name -> host name */
+    GHashTable *tfo_map;        /* GL3 path: guest transform feedback name -> host name */
+    GLenum draw_buffer, read_buffer;   /* guest values while the surface is bound */
+    bool in_begin;              /* between glBegin and glEnd: never switch contexts then */
+    bool quad_split;            /* Mesa: this glBegin(GL_QUADS) runs as one GL_POLYGON per quad */
+    uint8_t quad_n;             /* vertices of the current quad so far */
+    /* Mesa backends (zink ignores polygon stipple): emulated for fixed-function fragments */
+    uint8_t stipple[128];       /* the guest's glPolygonStipple pattern */
+    bool stipple_set, stipple_dirty, stipple_on;
+    GLuint stipple_tex, stipple_prog;   /* internal names */
+    GLint st_active, st_tex, st_prog;   /* guest state saved while the emulation is on */
+    /* Mesa backends (zink draws GL_POINT_SMOOTH points as squares): emulated likewise */
+    bool psmooth_on;
+    GLuint psmooth_prog;                /* internal name */
+    GLint ps_active, ps_prog, ps_replace;   /* guest state saved while it is on */
+    GLfloat ps_size;
+    GLint prog_error_pos;       /* GL_PROGRAM_ERROR_POSITION_ARB after the last glProgramStringARB
+                                   (answered here: querying it crashes the NVIDIA driver) */
+    GLuint so_fbo;              /* zero-copy scanout: fbo for the shared buffer being written
+                                   (a name of this context's own, like surf_fbo / rd_fbo) */
+    GLuint so_fbo_tex;          /* texture attached to so_fbo */
+} QEGLContext;
+
+struct QEGLRingState {
+    uint32_t cur_ctx;           /* guest ctx id current on this ring */
+    uint8_t *map;               /* host mapping of the ring buffer (guest RAM) */
+    uint64_t map_gpa;
+    uint64_t map_len;
+    bool owned;                 /* map is a host buffer (replay), not guest RAM */
+    bool active;                /* process_ring() of this ring is on the stack */
+};
+
+/* Execution context passed to GL command handlers */
+typedef struct QEGLExec {
+    QEGLState *s;
+    int ring;
+    QEGLContext *ctx;           /* current context of this ring, may be NULL */
+    uint8_t *shm;               /* BAR2 base */
+    uint32_t shm_size;
+    uint8_t *rbuf;              /* ring buffer (host mapping of guest RAM) */
+    uint32_t rbuf_size;
+} QEGLExec;
+
+/* qegl-gl.c */
+int qegl_exec_gl(QEGLExec *e, uint32_t index, const uint8_t *p, uint32_t len);
+void qegl_note_gl_error(QEGLExec *e, GLenum err);
+void qegl_fbo_detach_all(GLenum target);
+void qegl_group_fbos_release(QEGLGroup *g);
+void qegl_gl_init(void);
+extern bool qegl_gl_avail[];
+/* Mesa backends: qegl_glfn[] from a private opengl32.dll (qegl_glfn_on is then set) */
+extern HMODULE qegl_mesa_module;
+int qegl_glfn_resolve(bool with_context);
+bool qegl_has_gl_extension(const char *ext);
+
+/* qegl-gl.c: client-memory arrays of a draw command (also used by the GL3 path) */
+int qegl_apply_client_arrays(const uint8_t **p, const uint8_t *end);
+
+/* qegl-gl3.c: OpenGL 3.x/4.x commands (QEGL_OP_GL3_BASE) */
+extern bool qegl_gl3_ok;
+void qegl_gl3_init(void);
+int qegl_exec_gl3(QEGLExec *e, uint32_t index, const uint8_t *p, uint32_t len);
+const char *qegl_gl3_name(uint32_t index);
+void qegl_gl3_ctx_free(QEGLContext *c);
+void qegl_gl3_group_free(QEGLGroup *g);
+void qegl3_fix_read_binding(QEGLExec *e);
+GLuint qegl3_map_vao(QEGLExec *e, uint32_t name, bool create);
+GLuint qegl3_map_tfo(QEGLExec *e, uint32_t name, bool create);
+GLuint qegl3_map_sampler(QEGLExec *e, uint32_t name, bool create);
+
+/* qegl.c */
+QEGLSurface *qegl_find_surface(QEGLState *s, uint32_t id);
+GLuint qegl_internal_name(QEGLState *s);
+void qegl_bind_draw_surface(QEGLExec *e);
+void qegl_apply_draw_buffer(QEGLExec *e);
+extern int qegl_debug;
+extern volatile uint32_t qegl_dbg_op;       /* op being executed (diagnostics) */
+
+#endif
diff --git a/hw/display/qegl_proto.h b/hw/display/qegl_proto.h
new file mode 100644
index 0000000..d21a7a1
--- /dev/null
+++ b/hw/display/qegl_proto.h
@@ -0,0 +1,214 @@
+/*
+ * qegl_proto.h - guest <-> host protocol of the "qegl" paravirtual GL device
+ * (QEMU device hw/display/qegl.c, guest kext QemuGLAccel + GL driver QemuGLDriver).
+ *
+ * Shared by the host (modern gcc) and the Tiger guest (gcc-4.0): plain C, fixed-size
+ * little-endian fields only.
+ *
+ * Device: PCI 1234:5145, class 0x0b40 (co-processor; a display class would make Apple's
+ * IONDRVFramebuffer bind to it).
+ *   BAR0  4 KiB MMIO registers (QEGL_REG_*)
+ *   BAR2  shared memory (RAM on the host, default 4 MiB):
+ *         [0, 4K)                 struct qegl_shm_header
+ *         [4K, 4K + 64*64)        struct qegl_ring_desc[QEGL_MAX_RINGS]
+ *         [64K, 128K)             hardware cursor image (QEGL_CURSOR_OFF)
+ *
+ * Ring buffers live in ordinary guest RAM (physically contiguous, allocated by the guest
+ * kext) - the BAR is mapped uncached in the guest, which made bulk writes crawl (~150 MB/s).
+ * Each GL client (one per guest GL context or per process) owns one ring. The guest writes
+ * commands at `tail`, then writes the ring index to QEGL_REG_DOORBELL. The host's render
+ * thread executes commands from `head` to `tail` in order and publishes `head` and
+ * `fence_done`. Rings are processed in doorbell order, so cross-ring ordering follows the
+ * order in which guests kick.
+ */
+#ifndef QEGL_PROTO_H
+#define QEGL_PROTO_H
+
+#define QEGL_PCI_VENDOR     0x1234
+#define QEGL_PCI_DEVICE     0x5145
+#define QEGL_MAGIC          0x31474551u     /* "QEG1" */
+#define QEGL_VERSION        1
+
+#define QEGL_MAX_RINGS      64
+#define QEGL_SHM_HEADER_OFF 0x0
+#define QEGL_RING_TABLE_OFF 0x1000
+
+/* BAR0 registers (32-bit) */
+#define QEGL_REG_MAGIC      0x00    /* RO  QEGL_MAGIC */
+#define QEGL_REG_VERSION    0x04    /* RO  QEGL_VERSION */
+#define QEGL_REG_SHM_SIZE   0x08    /* RO  BAR2 size in bytes */
+#define QEGL_REG_STATUS     0x0c    /* RO  QEGL_STATUS_* */
+#define QEGL_REG_DOORBELL   0x10    /* WO  ring index with new commands */
+#define QEGL_REG_RING_RESET 0x14    /* WO  ring index: drop all host objects owned by it */
+#define QEGL_REG_IRQ_STATUS 0x18    /* RW1C (reserved) */
+
+/* Hardware cursor (drawn by the host UI, e.g. as the SDL window's mouse pointer). Driven by
+   the guest framebuffer driver (QemuVGA), independent of GL. Coordinates are framebuffer
+   pixels of QEMU console 0. */
+#define QEGL_REG_CURSOR_CAPS    0x20  /* RO  max cursor width and height, 0 = no hardware cursor */
+#define QEGL_REG_CURSOR_SIZE    0x24  /* RW  width | height << 16 of the image at QEGL_CURSOR_OFF */
+#define QEGL_REG_CURSOR_HOT     0x28  /* RW  hot spot x | y << 16, relative to the image */
+#define QEGL_REG_CURSOR_DEFINE  0x2c  /* WO  take the image: 32-bit ARGB, straight alpha, rows of
+                                         width * 4 bytes */
+#define QEGL_REG_CURSOR_POS     0x30  /* WO  image top-left x | y << 16 (signed 16-bit each) */
+#define QEGL_REG_CURSOR_VISIBLE 0x34  /* WO  0 hidden, 1 shown */
+
+/* Display timing: the refresh rate of the host monitor the VM's window is on (the primary
+   monitor when there is no window), and the vertical-blank rate the guest should emulate:
+   host / ceil(host / max) (an exact divisor, so every guest frame lasts the same number of host
+   refreshes: 480 -> 120, 240 -> 120, 144 -> 72, 60 -> 60), max = device property vbl_max
+   (default 120); vbl_hz=N forces N. Both in millihertz, 0 = unknown; re-detected every 2 s. */
+#define QEGL_REG_HOST_REFRESH   0x38  /* RO  mHz */
+#define QEGL_REG_VBL_RATE       0x3c  /* RO  mHz */
+
+#define QEGL_CURSOR_OFF     0x10000   /* BAR2 offset of the cursor image */
+#define QEGL_CURSOR_MAX     128       /* largest cursor, both dimensions */
+
+/* qegl_shm_header.features */
+#define QEGL_FEAT_APP_GL    0x1     /* applications (and so GPU Core Image) get the pass-through
+                                       renderer by default: the host has the fixes that make
+                                       that safe (qegl device property apps=1, the default) */
+#define QEGL_FEAT_RING_BARRIER 0x2  /* the host understands QEGL_OP_RING_BARRIER */
+#define QEGL_FEAT_GL3       0x4     /* the host executes QEGL_OP_GL3_BASE commands: OpenGL 3.x/4.x
+                                       entry points QemuGLDriver hands out through
+                                       glGetPointerv(QEGL_INTERFACE_PNAME) (WebGL 2) */
+
+#define QEGL_STATUS_GL_READY   0x1  /* host GL context created */
+#define QEGL_STATUS_GL_FAILED  0x2
+
+/* BAR2 offset 0: filled by the host at reset */
+struct qegl_shm_header {
+    unsigned int magic;             /* QEGL_MAGIC */
+    unsigned int version;
+    unsigned int shm_size;
+    unsigned int max_rings;
+    unsigned int max_texture_size;
+    unsigned int max_texture_units;
+    volatile unsigned int hw_cursor;    /* guest (QemuVGA): 1 while the cursor is a hardware
+                                           cursor, i.e. never drawn into VRAM */
+    unsigned int features;              /* host: QEGL_FEAT_* */
+    unsigned int reserved[8];
+    char gl_vendor[64];
+    char gl_renderer[128];
+    char gl_version[64];
+};
+
+/* BAR2 offset QEGL_RING_TABLE_OFF + 64 * index */
+struct qegl_ring_desc {
+    unsigned int gpa_lo;            /* guest: ring buffer guest-physical address (0 = unused) */
+    unsigned int size;              /* guest: bytes, power of two, >= 64K */
+    volatile unsigned int tail;     /* guest: producer byte count (monotonic, wraps 2^32) */
+    volatile unsigned int head;     /* host:  consumer byte count */
+    volatile unsigned int fence_done; /* host: value of the last executed QEGL_OP_FENCE */
+    volatile unsigned int error;    /* host: first error code seen (QEGL_ERR_*), sticky */
+    volatile unsigned int busy;     /* host: 1 while executing this ring */
+    unsigned int owner_pid;         /* guest: informational */
+    volatile unsigned int gl_error; /* host: first glGetError() seen since the guest cleared
+                                       it (checked after every batch, so glGetError never
+                                       needs a round trip) */
+    unsigned int gpa_hi;            /* guest: high half of the ring address */
+    volatile unsigned int reset_ack;/* host: incremented after a QEGL_REG_RING_RESET */
+    unsigned int reserved[5];
+};
+
+#define QEGL_ERR_NONE       0
+#define QEGL_ERR_BAD_CMD    1
+#define QEGL_ERR_BAD_OBJECT 2
+#define QEGL_ERR_RANGE      3
+#define QEGL_ERR_GL         4
+
+/*
+ * Commands: 8-byte aligned, never wrap. If a command does not fit before the end of the
+ * buffer the guest writes QEGL_OP_PAD over the remainder and starts at offset 0.
+ */
+struct qegl_cmd {
+    unsigned int op;                /* QEGL_OP_* or QEGL_OP_GL_BASE + dispatch index */
+    unsigned int len;               /* total bytes including this header, multiple of 8 */
+};
+
+#define QEGL_ALIGN(n)       (((n) + 7u) & ~7u)
+
+enum {
+    QEGL_OP_NOP = 0,
+    QEGL_OP_PAD = 1,                /* skip to the end of the buffer */
+    QEGL_OP_FENCE = 2,              /* u32 value: set fence_done once everything before ran */
+    QEGL_OP_RING_BARRIER = 3,       /* u32 ring, u32 tail: first execute that ring's commands up
+                                       to byte count `tail` (published). The guest driver puts
+                                       one in front of a context's commands when the process
+                                       last used another context, so work spread over several
+                                       contexts (Core Image: render a pbuffer in one, texture
+                                       from it in another) runs in program order. Only sent
+                                       when the host has QEGL_FEAT_RING_BARRIER. */
+
+    /* objects; ids are chosen by the guest (kext hands out globally unique ids) */
+    QEGL_OP_CTX_CREATE = 0x10,      /* u32 ctx, u32 share_group, u32 flags */
+    QEGL_OP_CTX_DESTROY = 0x11,     /* u32 ctx */
+    QEGL_OP_MAKE_CURRENT = 0x12,    /* u32 ctx, u32 draw_surf, u32 read_surf (0 = none) */
+    QEGL_OP_SURF_CREATE = 0x18,     /* u32 surf, u32 w, u32 h, u32 flags (QEGL_SURF_*) */
+    QEGL_OP_SURF_RESIZE = 0x19,     /* u32 surf, u32 w, u32 h */
+    QEGL_OP_SURF_DESTROY = 0x1a,    /* u32 surf */
+    QEGL_OP_SURF_SWAP = 0x1b,       /* u32 surf: back -> front */
+    QEGL_OP_SURF_PRESENT = 0x1c,    /* struct qegl_present */
+    QEGL_OP_SURF_READ = 0x1d,       /* struct qegl_surf_read: front buffer -> guest memory */
+    QEGL_OP_SURF_PRESENT_RECTS = 0x1e, /* struct qegl_present_rects (kernel surface flush) */
+    QEGL_OP_SURF_TEXIMAGE = 0x1f,   /* u32 surf, target, internalformat, w, h: copy the surface's
+                                       (pbuffer's) color buffer into the texture bound to target
+                                       (CGLTexImagePBuffer) */
+
+    QEGL_OP_GL_BASE = 0x1000,       /* + GLIFunctionDispatch index: generated GL calls */
+    QEGL_OP_GL3_BASE = 0x2000       /* + GL3 index (qegl3_index.h, gen_gl3.py): GL 3.x/4.x calls
+                                       that Tiger's dispatch table has no slot for. Same wire
+                                       format as the GL_BASE commands. */
+};
+
+#define QEGL_SURF_DOUBLE    0x01
+#define QEGL_SURF_DEPTH     0x02
+#define QEGL_SURF_STENCIL   0x04
+#define QEGL_SURF_ALPHA     0x08
+#define QEGL_SURF_SHARED    0x10    /* belongs to the share group, not to the creating context's
+                                       ring (pbuffers: every context of the process may draw to
+                                       them): when that ring goes away, another ring using the
+                                       group takes it over; freed with the group's last ring */
+
+/* host surface id of an IOAccelSurface (CGS surface id), as used by GL driver and kernel */
+#define QEGL_ACCEL_SURF_ID(sid)  (0x80000000u | (sid))
+#define QEGL_IS_ACCEL_SURF(id)   (((id) & 0x80000000u) != 0)
+/* ring 0 belongs to the kernel extension (surface flushes) */
+#define QEGL_KERNEL_RING    0
+
+/* Copy a rectangle of a surface's front buffer into guest physical memory (the IOFramebuffer
+   VRAM), as 32-bit BGRA ("ARGB8888" little endian), top-down rows. */
+struct qegl_present {
+    unsigned int surf;
+    unsigned int x, y, w, h;        /* source rect in surface coords, y from top */
+    unsigned int dst_x, dst_y;      /* destination pixel in the framebuffer */
+    unsigned int dst_pitch;         /* bytes per framebuffer row */
+    unsigned int dst_gpa_lo, dst_gpa_hi; /* guest physical address of framebuffer pixel (0,0) */
+    unsigned int dst_w, dst_h;      /* framebuffer size, for clipping */
+};
+
+/* Copy the parts of a surface covered by a list of framebuffer rectangles to the framebuffer
+   (IOAccelSurface flush). The surface's (0,0) sits at framebuffer (bounds_x, bounds_y). */
+struct qegl_rect {
+    int x, y, w, h;
+};
+
+struct qegl_present_rects {
+    unsigned int surf;
+    unsigned int dst_gpa_lo, dst_gpa_hi;
+    unsigned int dst_pitch, dst_w, dst_h;
+    int bounds_x, bounds_y;
+    unsigned int count;
+    struct qegl_rect rects[1];      /* count entries */
+};
+
+/* Read back a rectangle into the ring (reply_off = offset within the ring), top-down BGRA rows. */
+struct qegl_surf_read {
+    unsigned int surf;
+    unsigned int x, y, w, h;
+    unsigned int reply_off;
+    unsigned int reply_pitch;
+    unsigned int buffer;            /* 0 = front, 1 = back */
+};
+
+#endif /* QEGL_PROTO_H */
diff --git a/hw/input/hid.c b/hw/input/hid.c
index 0f28827..099dc44 100644
--- a/hw/input/hid.c
+++ b/hw/input/hid.c
@@ -133,13 +133,24 @@ static void hid_pointer_event(DeviceState *dev, QemuConsole *src,
         }
         break;
 
-    case INPUT_EVENT_KIND_ABS:
+    case INPUT_EVENT_KIND_ABS: {
+        int32_t value = evt->abs.value;
+
+        /*
+         * Some guests (e.g. Mac OS X 10.4) map the tablet range onto a larger
+         * area than the screen; pre-shrink so the pointer lands where it is.
+         */
+        if (hs->abs_scale > 1000) {
+            const int32_t mid = (INPUT_EVENT_ABS_MAX + 1) / 2;
+            value = mid + (int64_t)(value - mid) * 1000 / hs->abs_scale;
+        }
         if (evt->abs.axis == INPUT_AXIS_X) {
-            e->xdx = evt->abs.value;
+            e->xdx = value;
         } else if (evt->abs.axis == INPUT_AXIS_Y) {
-            e->ydy = evt->abs.value;
+            e->ydy = value;
         }
         break;
+    }
 
     case INPUT_EVENT_KIND_BTN:
         if (evt->btn.down) {
diff --git a/hw/net/e1000.c b/hw/net/e1000.c
index 202ad40..a2e456f 100644
--- a/hw/net/e1000.c
+++ b/hw/net/e1000.c
@@ -242,7 +242,13 @@ static const uint16_t phy_reg_init[] = {
     [MII_STAT1000] = MII_STAT1000_HALF | MII_STAT1000_FULL |
                      MII_STAT1000_ROK | MII_STAT1000_LOK,
     [M88E1000_PHY_SPEC_CTRL] = 0x360,
-    [M88E1000_PHY_SPEC_STATUS] = 0xac00,
+    /*
+     * Bit 3 (0x0008) set as well: Mac OS X 10.4's AppleIntel8254XEthernet only reports the
+     * link up after a link-status-change interrupt if STATUS.LU and this bit are both set;
+     * without it every LSC (including the one ending autonegotiation) reads as "Link Down"
+     * and DHCP never starts on interfaces configured at boot. Other e1000 drivers ignore it.
+     */
+    [M88E1000_PHY_SPEC_STATUS] = 0xac08,
     [M88E1000_EXT_PHY_SPEC_CTRL] = 0x0d60,
 };
 
diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c
index ae19d60..da75da0 100644
--- a/hw/usb/dev-hid.c
+++ b/hw/usb/dev-hid.c
@@ -43,6 +43,7 @@ struct USBHIDState {
     uint32_t usb_version;
     char *display;
     uint32_t head;
+    uint32_t abs_scale;
 };
 
 #define TYPE_USB_HID "usb-hid"
@@ -727,8 +728,10 @@ static void usb_hid_initfn(USBDevice *dev, int kind,
 
 static void usb_tablet_realize(USBDevice *dev, Error **errp)
 {
+    USBHIDState *us = USB_HID(dev);
 
     usb_hid_initfn(dev, HID_TABLET, &desc_tablet, &desc_tablet2, errp);
+    us->hid.abs_scale = us->abs_scale;
 }
 
 static void usb_mouse_realize(USBDevice *dev, Error **errp)
@@ -797,6 +800,7 @@ static const Property usb_tablet_properties[] = {
         DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2),
         DEFINE_PROP_STRING("display", USBHIDState, display),
         DEFINE_PROP_UINT32("head", USBHIDState, head, 0),
+        DEFINE_PROP_UINT32("abs-scale", USBHIDState, abs_scale, 1000),
 };
 
 static void usb_tablet_class_initfn(ObjectClass *klass, const void *data)
diff --git a/include/hw/input/hid.h b/include/hw/input/hid.h
index 6a9d7bf..4f262bc 100644
--- a/include/hw/input/hid.h
+++ b/include/hw/input/hid.h
@@ -40,6 +40,7 @@ struct HIDState {
     uint32_t head; /* index into circular queue */
     uint32_t n;
     int kind;
+    uint32_t abs_scale; /* tablet: shrink coordinates about the centre, in permille */
     int32_t protocol;
     uint8_t idle;
     bool idle_pending;
diff --git a/include/ui/console.h b/include/ui/console.h
index 3634956..81a5c63 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -172,6 +172,7 @@ typedef struct DisplayScanout {
 
 typedef struct DisplayState DisplayState;
 typedef struct DisplayGLCtx DisplayGLCtx;
+typedef struct QemuD3DScanout QemuD3DScanout;   /* ui/d3d-scanout.h (Windows) */
 
 typedef struct DisplayChangeListenerOps {
     const char *dpy_name;
@@ -236,6 +237,12 @@ typedef struct DisplayChangeListenerOps {
     void (*dpy_gl_update)(DisplayChangeListener *dcl,
                           uint32_t x, uint32_t y, uint32_t w, uint32_t h);
 
+    /*
+     * optional (Windows): the listener can show frames that a device renders on the host
+     * GPU as shared D3D11 textures, without the console surface (ui/d3d-scanout.h)
+     */
+    QemuD3DScanout *(*dpy_d3d_scanout)(DisplayChangeListener *dcl);
+
 } DisplayChangeListenerOps;
 
 struct DisplayChangeListener {
@@ -381,6 +388,20 @@ int qemu_console_get_window_id(QemuConsole *con);
 /* Set the low-level window id for the console */
 void qemu_console_set_window_id(QemuConsole *con, int window_id);
 
+/*
+ * Zero-copy scanout: the D3D11 sink of the display showing @con, if every listener of @con
+ * can take frames that way (none needs the surface's pixels for each frame); else NULL.
+ * Main loop only.
+ */
+QemuD3DScanout *qemu_console_get_d3d_scanout(QemuConsole *con);
+/*
+ * A device whose frames can reach the display without passing through the console surface
+ * registers a function that brings the surface up to date. It is called (main loop, BQL)
+ * before the surface is read for a screendump.
+ */
+void qemu_console_set_surface_sync(QemuConsole *con, void (*sync)(void *opaque),
+                                   void *opaque);
+
 void qemu_console_resize(QemuConsole *con, int width, int height);
 DisplaySurface *qemu_console_surface(QemuConsole *con);
 void coroutine_fn qemu_console_co_wait_update(QemuConsole *con);
diff --git a/include/ui/d3d-scanout.h b/include/ui/d3d-scanout.h
new file mode 100644
index 0000000..88cd8d6
--- /dev/null
+++ b/include/ui/d3d-scanout.h
@@ -0,0 +1,80 @@
+/*
+ * Zero-copy scanout to a Direct3D 11 display (Windows hosts)
+ *
+ * A device that renders a console's picture on the host GPU (qegl: OpenGL through NVIDIA's
+ * driver or Mesa zink) can hand its frames to a display that composes with Direct3D 11
+ * (ui/d3d11.c) without reading them back to guest memory:
+ *
+ *  - The display owns the D3D11 device. On request it allocates a "ring" of shared textures
+ *    for one framebuffer size (ring_create) and returns NT handles that the producer imports
+ *    (OpenGL: GL_EXT_memory_object_win32 + glTexStorageMem2DEXT; Vulkan:
+ *    VK_KHR_external_memory_win32), plus a shared ID3D11Fence (OpenGL:
+ *    GL_EXT_semaphore_win32 with GL_HANDLE_TYPE_D3D12_FENCE_EXT).
+ *  - For each frame the producer acquires a buffer, renders the frame into it (it must hold
+ *    the complete picture: each buffer is a full frame) and publishes it with the damaged
+ *    rectangle. With fence_value != 0 the display makes its GPU wait until the producer has
+ *    signalled the ring's fence to that value; fence_value == 0 means the producer has
+ *    already waited for its GPU work to complete.
+ *  - The display shows the newest published frame at its next refresh, and hands buffers
+ *    back by itself once its GPU has finished reading them. A frame published before an
+ *    older one was shown simply replaces it.
+ *  - The console's DisplaySurface is NOT updated by such frames. The producer keeps it
+ *    current on demand (qemu_console_set_surface_sync(), used by screendump) and before it
+ *    stops using the ring (disable).
+ *
+ * The display shows ring frames only while their size matches its console surface; when the
+ * guest changes modes it falls back to the surface until frames of the new size arrive.
+ *
+ * Textures: DXGI_FORMAT_R8G8B8A8_UNORM (GL_RGBA8 / VK_FORMAT_R8G8B8A8_UNORM on import; a
+ * BGRA texture would come out with red and blue swapped on NVIDIA's GL and Vulkan), rows
+ * top-down, i.e. an OpenGL import sees the picture upside down. Alpha is ignored.
+ *
+ * Threads: ring_create, ring_destroy, acquire, publish and disable may be called from any
+ * thread (the producer's render thread); the display serialises them internally.
+ * qemu_console_get_d3d_scanout() is main-loop only.
+ */
+#ifndef UI_D3D_SCANOUT_H
+#define UI_D3D_SCANOUT_H
+
+#ifdef _WIN32
+#include <windows.h>
+
+#define QEMU_D3D_SCANOUT_BUFFERS 3
+
+typedef struct QemuD3DScanoutRing {
+    uint32_t width, height;
+    HANDLE texture[QEMU_D3D_SCANOUT_BUFFERS];   /* NT handles; the producer may close them */
+    HANDLE fence;                               /* NT handle of the shared ID3D11Fence */
+    LUID adapter;                               /* the display's adapter: import only on it */
+} QemuD3DScanoutRing;
+
+typedef struct QemuD3DScanoutOps {
+    /* allocate a ring for a width x height framebuffer; NULL on failure */
+    QemuD3DScanoutRing *(*ring_create)(void *sink, uint32_t width, uint32_t height);
+    /* the producer no longer uses the ring (the display frees it when it is done with it) */
+    void (*ring_destroy)(void *sink, QemuD3DScanoutRing *ring);
+    /*
+     * a buffer to render the next frame into: its index; -1 if none is free right now (the
+     * display is still copying out of them), -2 if the console does not show a mode of the
+     * ring's size at the moment (present through the console surface instead)
+     */
+    int (*acquire)(void *sink, QemuD3DScanoutRing *ring);
+    /* hand back an acquired buffer without publishing it */
+    void (*release)(void *sink, QemuD3DScanoutRing *ring, int buffer);
+    /*
+     * the frame in @buffer is complete (or will be, once the fence reaches fence_value);
+     * frame_ns: QEMU_CLOCK_REALTIME when the guest presented it (latency statistics, 0 = n/a)
+     */
+    void (*publish)(void *sink, QemuD3DScanoutRing *ring, int buffer, uint64_t fence_value,
+                    int x, int y, int w, int h, int64_t frame_ns);
+    /* show the console surface again (the producer has brought it up to date) */
+    void (*disable)(void *sink);
+} QemuD3DScanoutOps;
+
+struct QemuD3DScanout {
+    const QemuD3DScanoutOps *ops;
+    void *sink;
+};
+
+#endif /* _WIN32 */
+#endif
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index 9daf5ec..5bf6ff0 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -58,6 +58,7 @@ struct sdl2_console {
 void sdl2_window_create(struct sdl2_console *scon);
 void sdl2_window_destroy(struct sdl2_console *scon);
 void sdl2_window_resize(struct sdl2_console *scon);
+void sdl2_update_scaling(struct sdl2_console *scon);
 void sdl2_poll_events(struct sdl2_console *scon);
 
 void sdl2_process_key(struct sdl2_console *scon,
diff --git a/meson.build b/meson.build
index 40e3f4e..8bf8f71 100644
--- a/meson.build
+++ b/meson.build
@@ -1644,6 +1644,16 @@ else
   sdl_image = not_found
 endif
 
+# Direct3D 11 display (ui/d3d11.c), Windows hosts
+d3d11 = not_found
+if host_os == 'windows' and have_system and cc.has_header('d3d11_4.h') and cc.has_header('dxgi1_6.h')
+  d3d11_libs = [cc.find_library('d3d11', required: false), cc.find_library('dxgi', required: false),
+                cc.find_library('dwmapi', required: false)]
+  if d3d11_libs[0].found() and d3d11_libs[1].found() and d3d11_libs[2].found()
+    d3d11 = declare_dependency(dependencies: d3d11_libs)
+  endif
+endif
+
 rbd = not_found
 if not get_option('rbd').auto() or have_block
   librados = cc.find_library('rados', required: get_option('rbd'))
@@ -2484,6 +2494,7 @@ config_host_data.set('CONFIG_RDMA', rdma.found())
 config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
 config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
 config_host_data.set('CONFIG_SDL', sdl.found())
+config_host_data.set('CONFIG_D3D11', d3d11.found())
 config_host_data.set('CONFIG_SDL_IMAGE', sdl_image.found())
 config_host_data.set('CONFIG_SECCOMP', seccomp.found())
 if seccomp.found()
@@ -4901,6 +4912,7 @@ if host_os == 'darwin'
 endif
 summary_info += {'D-Bus display':     dbus_display}
 summary_info += {'SDL support':       sdl}
+summary_info += {'D3D11 display':     d3d11}
 summary_info += {'SDL image support': sdl_image}
 summary_info += {'GTK support':       gtk}
 summary_info += {'pixman':            pixman}
diff --git a/qapi/ui.json b/qapi/ui.json
index b2c42a7..1eb6006 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1477,10 +1477,51 @@
 # @grab-mod: Modifier keys that should be pressed together with the
 #     "G" key to release the mouse grab.
 #
+# @integer-scale: Scale the guest display only by whole multiples,
+#     letterboxing the rest of the window, so that every guest pixel
+#     covers an exact square of host pixels.  2D renderer only (not
+#     with gl=on).  (default: off) (since 11.2)
+#
 # Since: 7.1
 ##
 { 'struct'  : 'DisplaySDL',
-  'data'    : { '*grab-mod'   : 'HotKeyMod' } }
+  'data'    : { '*grab-mod'      : 'HotKeyMod',
+                '*integer-scale' : 'bool' } }
+
+##
+# @DisplayD3D11:
+#
+# Direct3D 11 display options (Windows hosts).
+#
+# @integer-scale: Scale the guest display only by whole multiples,
+#     letterboxing the rest of the window or screen, so that every
+#     guest pixel covers an exact square of host pixels.  A picture
+#     bigger than the window is scaled down smoothly to fit.
+#     (default: on)
+#
+# @vsync: Present in step with the monitor's refresh.  With off,
+#     frames are shown immediately and may tear where the system
+#     allows tearing.  (default: on)
+#
+# @zero-copy: Show frames that a device renders on the host GPU
+#     (qegl) straight from shared GPU textures, without reading them
+#     back into guest memory.  (default: on)
+#
+# @parent: Show the display as a child window of this window (a
+#     Win32 HWND of another application, decimal or 0x hex) instead
+#     of a window of its own.  The embedding application positions,
+#     sizes and shows the child window; the two talk through the
+#     registered window messages "QEMU_D3D11_EMBED_CMD" and
+#     "QEMU_D3D11_EMBED_NOTIFY".  If the parent window goes away,
+#     the display continues in a window of its own.  (default: none)
+#
+# Since: 11.2
+##
+{ 'struct'  : 'DisplayD3D11',
+  'data'    : { '*integer-scale' : 'bool',
+                '*vsync'         : 'bool',
+                '*zero-copy'     : 'bool',
+                '*parent'        : 'uint64' } }
 
 ##
 # @DisplayType:
@@ -1517,6 +1558,9 @@
 #
 # @dbus: Start a D-Bus service for the display.  (Since 7.0)
 #
+# @d3d11: A Win32 window presenting with Direct3D 11 (Windows hosts).
+#     (Since 11.2)
+#
 # Since: 2.12
 ##
 { 'enum'    : 'DisplayType',
@@ -1529,7 +1573,8 @@
     { 'name': 'curses', 'if': 'CONFIG_CURSES' },
     { 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
     { 'name': 'spice-app', 'if': 'CONFIG_SPICE' },
-    { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' }
+    { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' },
+    { 'name': 'd3d11', 'if': 'CONFIG_D3D11' }
   ]
 }
 
@@ -1567,7 +1612,8 @@
       'egl-headless': { 'type': 'DisplayEGLHeadless',
                         'if': 'CONFIG_OPENGL' },
       'dbus': { 'type': 'DisplayDBus', 'if': 'CONFIG_DBUS_DISPLAY' },
-      'sdl': { 'type': 'DisplaySDL', 'if': 'CONFIG_SDL' }
+      'sdl': { 'type': 'DisplaySDL', 'if': 'CONFIG_SDL' },
+      'd3d11': { 'type': 'DisplayD3D11', 'if': 'CONFIG_D3D11' }
   }
 }
 
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 113f79a..b792fc1 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -664,6 +664,21 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
         apic_base.Reg64 = cpu_get_apic_base(X86_CPU(cpu)->apic_state);
         whpx_set_reg(cpu, WHvX64RegisterApicBase, apic_base);
     }
+
+    if (level == WHPX_LEVEL_RESET_STATE && whpx_irqchip_in_kernel()) {
+        /*
+         * With the in-kernel (Hyper-V) LAPIC, "waiting for SIPI" and "halted"
+         * live in WHvRegisterInternalActivityState, which writing the
+         * architectural registers above does not touch. Without this, APs
+         * that were running or halted when the machine was reset would start
+         * executing the firmware at the reset vector alongside the BSP
+         * instead of waiting for INIT/SIPI. Park the APs as at power-on and
+         * make the BSP runnable (the equivalent of KVM's mp_state reset).
+         */
+        WHV_REGISTER_VALUE activity = {};
+        activity.InternalActivity.StartupSuspend = !cpu_is_bsp(x86_cpu);
+        whpx_set_reg(cpu, WHvRegisterInternalActivityState, activity);
+    }
 }
 
 static int whpx_get_tsc(CPUState *cpu)
@@ -1946,6 +1961,86 @@ static int whpx_handle_halt(CPUState *cpu)
     return ret;
 }
 
+/*
+ * Bus ratio reported to Intel guests via IA32_PERF_STATUS. Pre-Nehalem
+ * guests (e.g. Mac OS X 10.4) derive the TSC frequency as FSB * ratio and
+ * program the local APIC timer in FSB ticks, so pick the smallest ratio
+ * whose FSB (TSC / ratio) does not exceed the APIC timer clock: timers then
+ * fire at worst slightly early, which the guest tolerates by re-arming.
+ * Can be overridden with the WHPX_INTEL_BUS_RATIO environment variable.
+ */
+static uint64_t whpx_intel_bus_ratio(CPUX86State *env)
+{
+    const char *override = getenv("WHPX_INTEL_BUS_RATIO");
+    uint64_t ratio;
+
+    if (override) {
+        ratio = strtoull(override, NULL, 0);
+    } else if (env->tsc_khz && env->apic_bus_freq) {
+        ratio = DIV_ROUND_UP(env->tsc_khz * 1000ULL, env->apic_bus_freq);
+    } else {
+        ratio = 10;
+    }
+    return MIN(MAX(ratio, 1), 31);
+}
+
+/* An Intel CPU model running on a non-Intel (e.g. AMD) host. */
+static bool whpx_intel_on_foreign_host(CPUX86State *env)
+{
+    static int foreign = -1;
+
+    if (foreign < 0) {
+        char vendor[CPUID_VENDOR_SZ + 1];
+
+        host_cpu_vendor_fms(vendor, NULL, NULL, NULL);
+        foreign = strcmp(vendor, CPUID_VENDOR_INTEL) != 0;
+    }
+    return foreign && IS_INTEL_CPU(env);
+}
+
+/*
+ * Intel MSRs that Hyper-V leaves unhandled on hosts lacking them (AMD), but
+ * which legacy Intel guests read to identify the CPU and its frequency.
+ */
+static bool whpx_intel_legacy_msr(CPUState *cpu, uint32_t msr, bool is_write,
+                                  uint64_t *val)
+{
+    CPUX86State *env = &X86_CPU(cpu)->env;
+    uint64_t ratio;
+
+    if (!IS_INTEL_CPU(env)) {
+        return false;
+    }
+
+    ratio = whpx_intel_bus_ratio(env);
+    switch (msr) {
+    case 0x17:  /* IA32_PLATFORM_ID */
+    case 0x2a:  /* EBL_CR_POWERON */
+    case 0x8b:  /* IA32_BIOS_SIGN_ID */
+    case 0x19c: /* IA32_THERM_STATUS */
+        if (!is_write) {
+            *val = 0;
+        }
+        return true;
+    case 0x198: /* IA32_PERF_STATUS: max ratio in 44:40, current in 15:8 */
+        if (!is_write) {
+            *val = (ratio << 40) | (ratio << 8);
+        }
+        return true;
+    case 0x199: /* IA32_PERF_CTL */
+        if (!is_write) {
+            *val = ratio << 8;
+        }
+        return true;
+    case 0x1a0: /* IA32_MISC_ENABLE: fast strings */
+        if (!is_write) {
+            *val = 1;
+        }
+        return true;
+    }
+    return false;
+}
+
 static int whpx_handle_hyperv_guestidle(CPUState *cpu)
 {
     X86CPU *x86_cpu = X86_CPU(cpu);
@@ -2461,6 +2556,12 @@ int whpx_vcpu_run(CPUState *cpu)
                 is_known_msr = 1;
             }
 
+            if (!is_known_msr) {
+                is_known_msr = whpx_intel_legacy_msr(cpu,
+                    vcpu->exit_ctx.MsrAccess.MsrNumber,
+                    vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite, &val);
+            }
+
             /*
              * For all unsupported MSR access we:
              *     ignore writes
@@ -2562,6 +2663,15 @@ int whpx_vcpu_run(CPUState *cpu)
                     if (x86_cpu->vmware_cpuid_freq) {
                         reg_values[1].Reg64 = env->tsc_khz;
                         reg_values[4].Reg64 = env->apic_bus_freq / 1000; /* Hz to KHz */
+                        /*
+                         * Firmware such as OpenCore takes this as the FSB
+                         * frequency; keep it equal to TSC / IA32_PERF_STATUS
+                         * ratio so legacy guests derive the exact TSC rate.
+                         */
+                        if (whpx_intel_on_foreign_host(env)) {
+                            reg_values[4].Reg64 = env->tsc_khz /
+                                                  whpx_intel_bus_ratio(env);
+                        }
                     }
                     break;
                 }
@@ -2820,6 +2930,14 @@ int whpx_init_vcpu(CPUState *cpu)
         env->apic_bus_freq = 1000000000;
     }
 
+    if (IS_INTEL_CPU(env) && cpu->cpu_index == 0) {
+        uint64_t ratio = whpx_intel_bus_ratio(env);
+        info_report("WHPX: TSC %" PRIu64 " kHz, APIC timer %" PRIu64
+                    " Hz, Intel bus ratio %" PRIu64 ", FSB %" PRIu64 " Hz",
+                    (uint64_t)env->tsc_khz, (uint64_t)env->apic_bus_freq,
+                    ratio, env->tsc_khz * 1000ULL / ratio);
+    }
+
     vcpu->interruptable = true;
     cpu->vcpu_dirty = true;
     cpu->accel = vcpu;
@@ -3039,9 +3157,16 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
     WHV_PROCESSOR_FEATURES_BANKS processor_features;
     WHV_PROCESSOR_PERFMON_FEATURES perfmon_features;
 
-    UINT32 cpuidExitList[] = {0x0, 0x1, 0x6, 0x7, 0xb, 0xd, 0x14, 0x24, 0x29, 0x1E,
+    /*
+     * Cache/topology leaves (2, 4, 5, 0xA, 0x80000005/6) are trapped too, so
+     * a guest CPU model of another vendor sees consistent values instead of
+     * the host's (AMD hosts report zeros for leaves 2 and 4).
+     */
+    UINT32 cpuidExitList[] = {0x0, 0x1, 0x2, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xd,
+        0x14, 0x24, 0x29, 0x1E,
         0x40000000, 0x40000001, 0x40000010, 0x80000000, 0x80000001,
-        0x80000002, 0x80000003, 0x80000004, 0x80000007, 0x80000008,
+        0x80000002, 0x80000003, 0x80000004, 0x80000005, 0x80000006,
+        0x80000007, 0x80000008,
         0x8000000A, 0x80000021, 0x80000022, 0xC0000000, 0xC0000001};
 
     X86MachineState *x86ms = X86_MACHINE(ms);
diff --git a/ui/console-priv.h b/ui/console-priv.h
index 4f731b4..2ec0955 100644
--- a/ui/console-priv.h
+++ b/ui/console-priv.h
@@ -25,6 +25,8 @@ struct QemuConsole {
     const GraphicHwOps *hw_ops;
     void *hw;
     CoQueue dump_queue;
+    void (*surface_sync)(void *opaque);
+    void *surface_sync_opaque;
 
     QTAILQ_ENTRY(QemuConsole) next;
 };
diff --git a/ui/console.c b/ui/console.c
index a8a2a24..fe0fb50 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -167,6 +167,10 @@ static void console_hw_update_bh(void *con)
 
 void qemu_console_co_wait_update(QemuConsole *con)
 {
+    if (con->surface_sync) {
+        /* frames shown by zero-copy scanout: bring the surface up to date first */
+        con->surface_sync(con->surface_sync_opaque);
+    }
     if (qemu_co_queue_empty(&con->dump_queue)) {
         /* Defer the update, it will restart the pending coroutines */
         aio_bh_schedule_oneshot(qemu_get_aio_context(),
@@ -219,6 +223,37 @@ void qemu_console_set_window_id(QemuConsole *con, int window_id)
     con->window_id = window_id;
 }
 
+void qemu_console_set_surface_sync(QemuConsole *con, void (*sync)(void *opaque),
+                                   void *opaque)
+{
+    con->surface_sync = sync;
+    con->surface_sync_opaque = opaque;
+}
+
+QemuD3DScanout *qemu_console_get_d3d_scanout(QemuConsole *con)
+{
+    DisplayChangeListener *dcl;
+    QemuD3DScanout *sc = NULL;
+
+    if (!con || !con->ds) {
+        return NULL;
+    }
+    QLIST_FOREACH(dcl, &con->ds->listeners, next) {
+        if (dcl->con && dcl->con != con) {
+            continue;
+        }
+        if (!dcl->ops->dpy_d3d_scanout || sc) {
+            /* a listener that needs the surface's pixels, or a second sink */
+            return NULL;
+        }
+        sc = dcl->ops->dpy_d3d_scanout(dcl);
+        if (!sc) {
+            return NULL;
+        }
+    }
+    return sc;
+}
+
 void qemu_console_hw_invalidate(QemuConsole *con)
 {
     if (con && con->hw_ops->invalidate) {
diff --git a/ui/d3d11.c b/ui/d3d11.c
new file mode 100644
index 0000000..0294b23
--- /dev/null
+++ b/ui/d3d11.c
@@ -0,0 +1,2976 @@
+/*
+ * QEMU Direct3D 11 display (Windows hosts)
+ *
+ * A Win32 window presenting through a DXGI flip-model swap chain. The window, its message
+ * loop, all Direct3D work and presentation run on a thread of their own, so neither window
+ * moves/resizes (modal loops) nor waiting for the GPU ever stall QEMU's main loop. Input is
+ * handed to the main loop through a bottom half.
+ *
+ * Two picture sources:
+ *  - the console's DisplaySurface (firmware, VGA, everything a device writes into guest
+ *    memory): dirty rectangles are uploaded into a texture;
+ *  - zero-copy scanout (ui/d3d-scanout.h): a device that renders on the host GPU (qegl)
+ *    hands over finished frames as shared D3D11 textures; nothing is read back.
+ *
+ * Behaviour follows our SDL setup (-display sdl,integer-scale=on): the window shows the
+ * guest picture 1:1 in physical pixels (per-monitor-v2 DPI aware), full screen
+ * (Ctrl+Alt+F) scales by the largest whole multiple and letterboxes, a mode bigger than the
+ * monitor's work area is scaled down smoothly to fit, and on a mode change the window keeps
+ * its centre and stays on its monitor. The guest's hardware cursor becomes the host pointer,
+ * created with CreateIconIndirect() so Windows' pointer-size setting does not stretch it
+ * (pixel for pixel: 1x in the window, the integer scale in full screen).
+ *
+ * Embedding (-display d3d11,parent=HWND): the window is created as a hidden child window of
+ * another application's window (a VM launcher). The launcher positions, sizes and shows it;
+ * QEMU never moves, resizes or shows it while embedded. The two talk through two registered
+ * window messages, always posted, never sent:
+ *  - "QEMU_D3D11_EMBED_CMD", launcher -> QEMU's window, wParam: 1 DETACH (become a top-level
+ *    window of its own), 2 ATTACH (lParam = new parent: become its hidden child again),
+ *    3 RELEASE_GRAB, 4 FOCUS;
+ *  - "QEMU_D3D11_EMBED_NOTIFY", QEMU -> the parent, wParam: 1 READY (lParam = QEMU's HWND),
+ *    2 SIZE (MAKELPARAM(w, h), the guest picture at 1x), 3 FULLSCREEN (Ctrl+Alt+F pressed),
+ *    4 GRAB (1/0), 5 DETACHED (lParam = HWND), 6 FOCUS (1/0), 7 HOTKEY (lParam = VK_UP,
+ *    VK_DOWN, 'B' or VK_HOME for Ctrl+Alt+Up/Down/B/Home, which do not reach the guest).
+ * While the child is hidden (or the launcher minimised) nothing is rendered, as when the
+ * top-level window is minimised. If the parent dies, Windows destroys the child window with
+ * it; QEMU then continues in a new top-level window (same device, new swap chain).
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#define COBJMACROS
+#include "qemu/osdep.h"
+#include <windowsx.h>
+#include <initguid.h>
+#include <d3d11_4.h>
+#include <dxgi1_6.h>
+#include <d3dcompiler.h>
+#include <dwmapi.h>
+#include <math.h>
+
+#include "qemu/main-loop.h"
+#include "qemu/bitmap.h"
+#include "qemu/thread.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "ui/kbd-state.h"
+#include "ui/surface.h"
+#include "ui/d3d-scanout.h"
+#include "ui/win32-kbd-hook.h"
+#include "system/runstate.h"
+#include "system/runstate-action.h"
+#include "system/system.h"
+#include "standard-headers/linux/input-event-codes.h"
+
+#define D3D_CLASS_NAME L"QEMU_D3D11"
+
+/* posted by test tools: dump the next presented frame (needs QEMU_D3D11_SNAP=file.ppm) */
+#define WM_D3D_SNAP (WM_APP + 1)
+
+/*
+ * Embedding protocol (see the top of the file). The launcher detects support by finding the
+ * UTF-16 text of the command message's name in the executable: keep these wide literals.
+ */
+#define D3D_EMBED_CMD_NAME      L"QEMU_D3D11_EMBED_CMD"
+#define D3D_EMBED_NOTIFY_NAME   L"QEMU_D3D11_EMBED_NOTIFY"
+
+enum {                          /* QEMU_D3D11_EMBED_CMD wParam */
+    EMBED_CMD_DETACH = 1,
+    EMBED_CMD_ATTACH = 2,
+    EMBED_CMD_RELEASE_GRAB = 3,
+    EMBED_CMD_FOCUS = 4,
+};
+
+enum {                          /* QEMU_D3D11_EMBED_NOTIFY wParam */
+    EMBED_N_READY = 1,
+    EMBED_N_SIZE = 2,
+    EMBED_N_FULLSCREEN = 3,
+    EMBED_N_GRAB = 4,
+    EMBED_N_DETACHED = 5,
+    EMBED_N_FOCUS = 6,
+    EMBED_N_HOTKEY = 7,
+};
+
+/*
+ * DirectComposition, the few methods used here. A flip-model swap chain cannot be made for a
+ * child window of another process (CreateSwapChainForHwnd: DXGI_ERROR_NOT_CURRENTLY_AVAILABLE),
+ * so an embedded display presents a composition swap chain shown by a DirectComposition
+ * visual on its window (as Chromium's GPU process does). mingw's dcomp.h declares these
+ * interfaces for C++ only (overloaded methods), hence these C vtables; unused slots are
+ * placeholders.
+ */
+typedef struct DCompDevice DCompDevice;
+typedef struct DCompTarget DCompTarget;
+typedef struct DCompVisual DCompVisual;
+
+typedef struct DCompDeviceVtbl {
+    HRESULT (STDMETHODCALLTYPE *QueryInterface)(DCompDevice *, REFIID, void **);
+    ULONG (STDMETHODCALLTYPE *AddRef)(DCompDevice *);
+    ULONG (STDMETHODCALLTYPE *Release)(DCompDevice *);
+    HRESULT (STDMETHODCALLTYPE *Commit)(DCompDevice *);
+    void *WaitForCommitCompletion, *GetFrameStatistics;
+    HRESULT (STDMETHODCALLTYPE *CreateTargetForHwnd)(DCompDevice *, HWND, BOOL, DCompTarget **);
+    HRESULT (STDMETHODCALLTYPE *CreateVisual)(DCompDevice *, DCompVisual **);
+} DCompDeviceVtbl;
+struct DCompDevice { const DCompDeviceVtbl *lpVtbl; };
+
+typedef struct DCompTargetVtbl {
+    HRESULT (STDMETHODCALLTYPE *QueryInterface)(DCompTarget *, REFIID, void **);
+    ULONG (STDMETHODCALLTYPE *AddRef)(DCompTarget *);
+    ULONG (STDMETHODCALLTYPE *Release)(DCompTarget *);
+    HRESULT (STDMETHODCALLTYPE *SetRoot)(DCompTarget *, DCompVisual *);
+} DCompTargetVtbl;
+struct DCompTarget { const DCompTargetVtbl *lpVtbl; };
+
+typedef struct DCompVisualVtbl {
+    HRESULT (STDMETHODCALLTYPE *QueryInterface)(DCompVisual *, REFIID, void **);
+    ULONG (STDMETHODCALLTYPE *AddRef)(DCompVisual *);
+    ULONG (STDMETHODCALLTYPE *Release)(DCompVisual *);
+    void *SetOffsetX[2], *SetOffsetY[2], *SetTransform[2], *SetTransformParent, *SetEffect;
+    void *SetBitmapInterpolationMode, *SetBorderMode, *SetClip[2];
+    HRESULT (STDMETHODCALLTYPE *SetContent)(DCompVisual *, IUnknown *);
+} DCompVisualVtbl;
+struct DCompVisual { const DCompVisualVtbl *lpVtbl; };
+
+/* IID_IDCompositionDevice */
+static const GUID d3d_iid_dcomp_device = {
+    0xc37ea93a, 0xe7aa, 0x450d, { 0xb1, 0x6f, 0x97, 0x46, 0xcb, 0x04, 0x07, 0xf3 }
+};
+
+#ifndef WM_GETDPISCALEDSIZE
+#define WM_GETDPISCALEDSIZE 0x02E4
+#endif
+
+/* busy refresh interval while there is input, as in the SDL backend */
+#define D3D_REFRESH_BUSY_MS 10
+#define D3D_IDLE_TICKS (2 * GUI_REFRESH_INTERVAL_DEFAULT / D3D_REFRESH_BUSY_MS + 1)
+
+enum { BUF_FREE, BUF_WRITING, BUF_READY, BUF_READING };
+
+typedef struct D3DRing {
+    QemuD3DScanoutRing pub;
+    ID3D11Texture2D *tex[QEMU_D3D_SCANOUT_BUFFERS];
+    ID3D11Fence *fence;
+    int state[QEMU_D3D_SCANOUT_BUFFERS];
+    uint64_t value[QEMU_D3D_SCANOUT_BUFFERS];   /* READY: producer fence value to wait for */
+    uint64_t done[QEMU_D3D_SCANOUT_BUFFERS];    /* READING: our fence value when copied */
+    int64_t t_publish[QEMU_D3D_SCANOUT_BUFFERS];   /* QPC */
+    int64_t frame_ns[QEMU_D3D_SCANOUT_BUFFERS];    /* the guest's present (QEMU_CLOCK_REALTIME) */
+    int latest;                 /* newest READY buffer, or -1 */
+    bool dead;                  /* the producer is done with it */
+    int refs;
+} D3DRing;
+
+/* input handed from the window thread to the main loop */
+enum {
+    DE_KEY, DE_MOUSE, DE_REL, DE_WHEEL, DE_RELEASE_ALL, DE_CLOSE, DE_REFRESH, DE_BUTTONS,
+    DE_HWUPDATE, DE_WINDOW_ID,
+};
+
+typedef struct D3DEvent {
+    uint8_t type;
+    bool down;
+    uint16_t code;              /* linux key code / InputButton / refresh ms */
+    int32_t x, y, w, h;
+    uint32_t buttons;
+} D3DEvent;
+
+/* things the main loop or the producer tell the window thread (D.pending) */
+enum {
+    PEND_SURFACE = 1 << 0,      /* new surface (size) */
+    PEND_DIRTY = 1 << 1,        /* surface rectangle to upload */
+    PEND_CURSOR = 1 << 2,       /* new guest cursor image */
+    PEND_MOUSE = 1 << 3,        /* guest cursor shown/hidden */
+    PEND_MOUSEMODE = 1 << 4,    /* absolute/relative pointer changed */
+    PEND_TITLE = 1 << 5,
+    PEND_DISABLE = 1 << 6,      /* back to the surface (scanout producer) */
+    PEND_QUIT = 1 << 7,
+};
+
+static struct {
+    DisplayOptions *opts;
+    DisplayChangeListener dcl;
+    QemuConsole *con;
+    QKbdState *kbd;
+    bool integer_scale, vsync, zero_copy, show_cursor, window_close;
+    Notifier mouse_mode_notifier;
+    QemuD3DScanout scanout;
+    int idle_ticks;
+    bool busy;
+    bool refresh_hold;          /* main loop: embedded and hidden, keep the slow refresh */
+
+    /* shared state, D.lock */
+    QemuMutex lock;
+    uint32_t pending;
+    DisplaySurface *surface;    /* the console's current surface (main loop) */
+    int dirty_x0, dirty_y0, dirty_x1, dirty_y1;
+    QEMUCursor *cursor_new;
+    bool guest_cursor_on;
+    bool absolute;
+    char title[256];
+
+    /* main loop only */
+    bool last_running;
+    int64_t last_hw_update_ms;
+
+    /* input queue, D.in_lock */
+    QemuMutex in_lock;
+    GArray *events;
+    QEMUBH *input_bh;
+    uint32_t main_buttons;
+
+    /* scanout rings, D.ring_lock */
+    QemuMutex ring_lock;
+    D3DRing *ring;              /* the producer's current ring */
+    GPtrArray *retired;         /* old rings with buffers we may still be reading */
+
+    /* window thread */
+    QemuThread thread;
+    HANDLE wake;                /* auto-reset: something in D.pending or a ring changed */
+    HANDLE ready;               /* the window thread has started (or failed) */
+    bool init_ok;
+    HWND hwnd;
+    ID3D11Device *dev;
+    ID3D11Device5 *dev5;
+    ID3D11DeviceContext *ctx;
+    ID3D11DeviceContext4 *ctx4;
+    IDXGISwapChain2 *swap;
+    HANDLE waitable;
+    ID3D11RenderTargetView *rtv;
+    ID3D11VertexShader *vs;
+    ID3D11PixelShader *ps;
+    ID3D11SamplerState *smp_point, *smp_linear;
+    ID3D11Texture2D *surf_tex;
+    ID3D11ShaderResourceView *surf_srv;
+    uint8_t *shadow;            /* what surf_tex holds (sw x sh, 4 bytes per pixel) */
+    bool shadow_valid;
+    int sw, sh;                 /* surface size as the window thread knows it */
+    ID3D11Texture2D *frame_tex;
+    ID3D11ShaderResourceView *frame_srv;
+    int fw, fh;
+    bool scanout_on;            /* showing a ring frame */
+    ID3D11Fence *rel_fence;     /* signalled after each copy out of a ring buffer */
+    uint64_t rel_value;
+    HANDLE rel_event;
+    LUID luid;
+    bool tearing;
+    bool need_render, slot_free;
+    int64_t t_frame;            /* QPC publish time of the ring frame waiting to be shown */
+    int cw, ch;                 /* client (= swap chain) size */
+    RECT view;                  /* where the picture is drawn, client coordinates */
+    double view_scale;
+    bool minimized;
+    bool fullscreen;
+    WINDOWPLACEMENT saved_placement;
+    bool window_shown;
+    bool focus;
+    bool mouse_inside;
+    bool grab;                  /* Ctrl+Alt+G: pointer confined, keyboard captured */
+    bool kbd_grab;              /* system keys (Win, Alt+Tab) go to the guest */
+    bool wt_absolute;
+    bool wt_cursor_on;
+    QEMUCursor *cursor;         /* current guest cursor image */
+    HCURSOR hcursor;
+    int cursor_scale;
+    uint32_t buttons;
+    int wheel_acc, hwheel_acc;
+    bool lctrl, lalt;           /* left Ctrl / Alt held (hotkeys) */
+    bool raw_input;
+    UINT dpi;
+
+    /* embedding (parent=): window thread */
+    bool embed;                 /* started with parent=: the embed protocol is on */
+    HWND parent;                /* current parent window; NULL while a top-level window */
+    UINT msg_cmd, msg_notify;
+    HWINEVENTHOOK ev_hook[2];   /* the parent process' windows shown/hidden/minimised */
+    bool parent_closed;         /* WM_SHOWWINDOW SW_PARENTCLOSING */
+    bool hidden;                /* embedded and not visible: render nothing */
+    bool paused;                /* minimised or hidden */
+    bool window_lost;           /* the window was destroyed without us asking (parent died) */
+    bool quitting;
+    RECT lost_rect;             /* where the lost window was on the screen */
+    RECT last_rect;             /* where our window was last seen on the screen */
+    int size_w, size_h;         /* the last SIZE notification */
+    DCompDevice *dcomp;         /* embed: composition swap chain shown by a DComp visual */
+    DCompTarget *dc_target;
+    DCompVisual *dc_visual;
+    unsigned long hk_held[BITS_TO_LONGS(KEY_CNT)];  /* hotkeys whose repeat/release we eat */
+
+    /* debugging: WM_D3D_SNAP dumps the next presented picture to $QEMU_D3D11_SNAP (PPM) */
+    bool snap_req;
+    int snap_n;
+
+    /* statistics (QEMU_D3D11_STATS=1) */
+    bool stats;
+    int64_t st_t0;
+    int st_presents, st_frames, st_surface_frames;
+    double st_lat_sum, st_lat_max;  /* publish -> Present call, ms */
+    double st_glat_sum, st_glat_max; /* guest present -> Present call, ms */
+    int st_glat_n;
+    int64_t cur_frame_ns;           /* guest present time of the frame being shown */
+    int st_lat_n;
+    double st_disp_sum, st_disp_max; /* publish -> on screen (frame statistics), ms */
+    int st_disp_n;
+    struct { UINT pc; int64_t t; } st_pend[16];
+    int st_pend_n;
+    double st_upload_ms;
+    int64_t qpf;
+} D;
+
+static UINT (WINAPI *p_GetDpiForWindow)(HWND);
+static BOOL (WINAPI *p_AdjustWindowRectExForDpi)(LPRECT, DWORD, BOOL, DWORD, UINT);
+static DPI_AWARENESS_CONTEXT (WINAPI *p_SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
+
+static int64_t qpc_now(void)
+{
+    LARGE_INTEGER t;
+    QueryPerformanceCounter(&t);
+    return t.QuadPart;
+}
+
+static double qpc_ms(int64_t d)
+{
+    return d * 1000.0 / D.qpf;
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* main loop side */
+
+static void d3d_wake(uint32_t pend)
+{
+    if (pend) {
+        qemu_mutex_lock(&D.lock);
+        D.pending |= pend;
+        qemu_mutex_unlock(&D.lock);
+    }
+    SetEvent(D.wake);
+}
+
+static void d3d_gfx_switch(DisplayChangeListener *dcl, DisplaySurface *s)
+{
+    qemu_mutex_lock(&D.lock);
+    D.surface = s;
+    D.dirty_x0 = 0;
+    D.dirty_y0 = 0;
+    D.dirty_x1 = surface_width(s);
+    D.dirty_y1 = surface_height(s);
+    D.pending |= PEND_SURFACE | PEND_DIRTY;
+    qemu_mutex_unlock(&D.lock);
+    SetEvent(D.wake);
+}
+
+static void d3d_gfx_update(DisplayChangeListener *dcl, int x, int y, int w, int h)
+{
+    if (w <= 0 || h <= 0) {
+        return;
+    }
+    qemu_mutex_lock(&D.lock);
+    if (D.pending & PEND_DIRTY) {
+        D.dirty_x0 = MIN(D.dirty_x0, x);
+        D.dirty_y0 = MIN(D.dirty_y0, y);
+        D.dirty_x1 = MAX(D.dirty_x1, x + w);
+        D.dirty_y1 = MAX(D.dirty_y1, y + h);
+    } else {
+        D.dirty_x0 = x;
+        D.dirty_y0 = y;
+        D.dirty_x1 = x + w;
+        D.dirty_y1 = y + h;
+    }
+    D.pending |= PEND_DIRTY;
+    qemu_mutex_unlock(&D.lock);
+    SetEvent(D.wake);
+}
+
+static bool d3d_check_format(DisplayChangeListener *dcl, pixman_format_code_t format)
+{
+    return format == PIXMAN_x8r8g8b8 || format == PIXMAN_a8r8g8b8;
+}
+
+static void d3d_update_title(void)
+{
+    const char *status = !runstate_is_running() ? " [Stopped]" : "";
+
+    qemu_mutex_lock(&D.lock);
+    if (qemu_name) {
+        snprintf(D.title, sizeof(D.title), "QEMU (%s-%d)%s", qemu_name,
+                 qemu_console_get_index(D.con), status);
+    } else {
+        snprintf(D.title, sizeof(D.title), "QEMU%s", status);
+    }
+    D.pending |= PEND_TITLE;
+    qemu_mutex_unlock(&D.lock);
+    SetEvent(D.wake);
+}
+
+static void d3d_refresh(DisplayChangeListener *dcl)
+{
+    if (D.last_running != runstate_is_running()) {
+        D.last_running = runstate_is_running();
+        d3d_update_title();
+    }
+    /*
+     * While ring frames are shown, the surface is not: scan it only every 250 ms to notice
+     * mode changes (under WHPX every scan finds the whole framebuffer dirty, which would
+     * otherwise mean a full upload each refresh).
+     */
+    if (!qatomic_read(&D.scanout_on) ||
+        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - D.last_hw_update_ms >= 250) {
+        D.last_hw_update_ms = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+        qemu_console_hw_update(dcl->con);
+    }
+    /* poll faster while the user is busy, like SDL (the window thread sets D.busy) */
+    if (D.refresh_hold) {
+        /* an embedded window that is hidden keeps the slow interval DE_REFRESH set */
+        qatomic_set(&D.busy, false);
+        D.idle_ticks = D3D_IDLE_TICKS;
+    } else if (qatomic_xchg(&D.busy, false)) {
+        D.idle_ticks = 0;
+        dcl->update_interval = D3D_REFRESH_BUSY_MS;
+    } else if (D.idle_ticks < D3D_IDLE_TICKS && ++D.idle_ticks == D3D_IDLE_TICKS) {
+        dcl->update_interval = GUI_REFRESH_INTERVAL_DEFAULT;
+    }
+}
+
+static void d3d_mouse_set(DisplayChangeListener *dcl, int x, int y, bool on)
+{
+    qemu_mutex_lock(&D.lock);
+    D.guest_cursor_on = on;
+    D.pending |= PEND_MOUSE;
+    qemu_mutex_unlock(&D.lock);
+    SetEvent(D.wake);
+}
+
+static void d3d_cursor_define(DisplayChangeListener *dcl, QEMUCursor *c)
+{
+    cursor_ref(c);
+    qemu_mutex_lock(&D.lock);
+    if (D.cursor_new) {
+        cursor_unref(D.cursor_new);
+    }
+    D.cursor_new = c;
+    D.pending |= PEND_CURSOR;
+    qemu_mutex_unlock(&D.lock);
+    SetEvent(D.wake);
+}
+
+static QemuD3DScanout *d3d_get_scanout(DisplayChangeListener *dcl)
+{
+    return D.zero_copy && D.dev5 && D.ctx4 ? &D.scanout : NULL;
+}
+
+static void d3d_mouse_mode_change(Notifier *n, void *data)
+{
+    qemu_mutex_lock(&D.lock);
+    D.absolute = qemu_input_is_absolute(D.con);
+    D.pending |= PEND_MOUSEMODE;
+    qemu_mutex_unlock(&D.lock);
+    SetEvent(D.wake);
+}
+
+static void d3d_vm_state_change(void *opaque, bool running, RunState state)
+{
+    D.last_running = running;
+    d3d_update_title();
+}
+
+static uint32_t d3d_bmap[INPUT_BUTTON__MAX] = {
+    [INPUT_BUTTON_LEFT]   = 1 << 0,
+    [INPUT_BUTTON_MIDDLE] = 1 << 1,
+    [INPUT_BUTTON_RIGHT]  = 1 << 2,
+    [INPUT_BUTTON_SIDE]   = 1 << 3,
+    [INPUT_BUTTON_EXTRA]  = 1 << 4,
+};
+
+static void d3d_key_event(unsigned int lnx, bool down)
+{
+    QemuConsole *con = D.con;
+
+    qkbd_state_key_event(D.kbd, lnx, down);
+    if (down && QEMU_IS_TEXT_CONSOLE(con)) {
+        QemuTextConsole *s = QEMU_TEXT_CONSOLE(con);
+        if (lnx == KEY_ENTER) {
+            qemu_text_console_put_keysym(s, '\n');
+        } else {
+            qemu_text_console_put_linux(s, lnx,
+                                        qkbd_state_modifier_get(D.kbd, QKBD_MOD_CTRL));
+        }
+    }
+}
+
+static void d3d_set_buttons(uint32_t b)
+{
+    if (b != D.main_buttons) {
+        qemu_input_update_buttons(D.con, d3d_bmap, D.main_buttons, b);
+        D.main_buttons = b;
+    }
+}
+
+/* main loop: input from the window thread */
+static void d3d_input_bh(void *opaque)
+{
+    g_autoptr(GArray) evs = NULL;
+    guint i;
+
+    qemu_mutex_lock(&D.in_lock);
+    evs = D.events;
+    D.events = g_array_new(false, false, sizeof(D3DEvent));
+    qemu_mutex_unlock(&D.in_lock);
+
+    for (i = 0; i < evs->len; i++) {
+        D3DEvent *e = &g_array_index(evs, D3DEvent, i);
+        switch (e->type) {
+        case DE_KEY:
+            d3d_key_event(e->code, e->down);
+            break;
+        case DE_MOUSE:
+            if (!qemu_console_is_graphic(D.con)) {
+                break;
+            }
+            d3d_set_buttons(e->buttons);
+            if (qemu_input_is_absolute(D.con)) {
+                qemu_input_queue_abs(D.con, INPUT_AXIS_X, e->x, 0, e->w);
+                qemu_input_queue_abs(D.con, INPUT_AXIS_Y, e->y, 0, e->h);
+            }
+            qemu_input_event_sync();
+            break;
+        case DE_BUTTONS:
+            d3d_set_buttons(e->buttons);
+            qemu_input_event_sync();
+            break;
+        case DE_REL:
+            if (!qemu_input_is_absolute(D.con)) {
+                qemu_input_queue_rel(D.con, INPUT_AXIS_X, e->x);
+                qemu_input_queue_rel(D.con, INPUT_AXIS_Y, e->y);
+                qemu_input_event_sync();
+            }
+            break;
+        case DE_WHEEL:
+            qemu_input_queue_btn(D.con, e->code, true);
+            qemu_input_event_sync();
+            qemu_input_queue_btn(D.con, e->code, false);
+            qemu_input_event_sync();
+            break;
+        case DE_RELEASE_ALL:
+            qkbd_state_lift_all_keys(D.kbd);
+            d3d_set_buttons(0);
+            qemu_input_event_sync();
+            break;
+        case DE_CLOSE:
+            if (!qemu_console_is_graphic(D.con) || D.window_close) {
+                shutdown_action = SHUTDOWN_ACTION_POWEROFF;
+                qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
+            }
+            break;
+        case DE_REFRESH:
+            D.refresh_hold = e->down;
+            qemu_console_listener_set_refresh(&D.dcl, e->code);
+            break;
+        case DE_HWUPDATE:
+            qemu_console_hw_update(D.con);
+            break;
+        case DE_WINDOW_ID:
+            qemu_console_set_window_id(D.con, e->x);
+            break;
+        }
+    }
+}
+
+/* window thread: queue an event for the main loop */
+static void d3d_post(const D3DEvent *e)
+{
+    qemu_mutex_lock(&D.in_lock);
+    if (e->type == DE_MOUSE && D.events->len) {
+        /* coalesce pointer moves that do not change the buttons */
+        D3DEvent *last = &g_array_index(D.events, D3DEvent, D.events->len - 1);
+        if (last->type == DE_MOUSE && last->buttons == e->buttons) {
+            *last = *e;
+            qemu_mutex_unlock(&D.in_lock);
+            return;
+        }
+    }
+    g_array_append_val(D.events, *e);
+    qemu_mutex_unlock(&D.in_lock);
+    qemu_bh_schedule(D.input_bh);
+    qatomic_set(&D.busy, true);
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* zero-copy scanout: called by the producer (any thread) */
+
+static void ring_unref(D3DRing *r)
+{
+    int i;
+
+    if (qatomic_fetch_dec(&r->refs) != 1) {
+        return;
+    }
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+        if (r->tex[i]) {
+            ID3D11Texture2D_Release(r->tex[i]);
+        }
+        if (r->pub.texture[i]) {
+            CloseHandle(r->pub.texture[i]);
+        }
+    }
+    if (r->fence) {
+        ID3D11Fence_Release(r->fence);
+    }
+    if (r->pub.fence) {
+        CloseHandle(r->pub.fence);
+    }
+    g_free(r);
+}
+
+static QemuD3DScanoutRing *sc_ring_create(void *sink, uint32_t w, uint32_t h)
+{
+    D3D11_TEXTURE2D_DESC td = {
+        .Width = w, .Height = h, .MipLevels = 1, .ArraySize = 1,
+        .Format = DXGI_FORMAT_R8G8B8A8_UNORM, .SampleDesc = { 1, 0 },
+        .Usage = D3D11_USAGE_DEFAULT,
+        .BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
+        .MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE,
+    };
+    D3DRing *r, *old;
+    HRESULT hr;
+    int i;
+
+    if (!D.dev5 || !w || !h || w > 16384 || h > 16384) {
+        return NULL;
+    }
+    r = g_new0(D3DRing, 1);
+    r->pub.width = w;
+    r->pub.height = h;
+    r->pub.adapter = D.luid;
+    r->latest = -1;
+    r->refs = 2;                /* the producer's and ours */
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+        IDXGIResource1 *res = NULL;
+        hr = ID3D11Device_CreateTexture2D(D.dev, &td, NULL, &r->tex[i]);
+        if (SUCCEEDED(hr)) {
+            hr = ID3D11Texture2D_QueryInterface(r->tex[i], &IID_IDXGIResource1, (void **)&res);
+        }
+        if (SUCCEEDED(hr)) {
+            hr = IDXGIResource1_CreateSharedHandle(res, NULL, DXGI_SHARED_RESOURCE_READ |
+                                                   DXGI_SHARED_RESOURCE_WRITE, NULL,
+                                                   &r->pub.texture[i]);
+            IDXGIResource1_Release(res);
+        }
+        if (FAILED(hr)) {
+            error_report("d3d11: shared texture %ux%u: 0x%lx", w, h, hr);
+            r->refs = 1;
+            ring_unref(r);
+            return NULL;
+        }
+    }
+    hr = ID3D11Device5_CreateFence(D.dev5, 0, D3D11_FENCE_FLAG_SHARED, &IID_ID3D11Fence,
+                                   (void **)&r->fence);
+    if (SUCCEEDED(hr)) {
+        hr = ID3D11Fence_CreateSharedHandle(r->fence, NULL, GENERIC_ALL, NULL, &r->pub.fence);
+    }
+    if (FAILED(hr)) {
+        error_report("d3d11: shared fence: 0x%lx", hr);
+        r->refs = 1;
+        ring_unref(r);
+        return NULL;
+    }
+    qemu_mutex_lock(&D.ring_lock);
+    old = D.ring;
+    D.ring = r;
+    if (old) {
+        g_ptr_array_add(D.retired, old);
+    }
+    qemu_mutex_unlock(&D.ring_lock);
+    SetEvent(D.wake);
+    return &r->pub;
+}
+
+static void sc_ring_destroy(void *sink, QemuD3DScanoutRing *pub)
+{
+    D3DRing *r = container_of(pub, D3DRing, pub);
+
+    qemu_mutex_lock(&D.ring_lock);
+    r->dead = true;
+    r->latest = -1;
+    qemu_mutex_unlock(&D.ring_lock);
+    ring_unref(r);
+    SetEvent(D.wake);
+}
+
+static int sc_acquire(void *sink, QemuD3DScanoutRing *pub)
+{
+    D3DRing *r = container_of(pub, D3DRing, pub);
+    int i, b = -1;
+    bool fits;
+
+    qemu_mutex_lock(&D.lock);
+    fits = D.surface && surface_width(D.surface) == (int)pub->width &&
+           surface_height(D.surface) == (int)pub->height;
+    qemu_mutex_unlock(&D.lock);
+    if (!fits) {
+        return -2;      /* the console shows another mode (for now): not via the ring */
+    }
+    qemu_mutex_lock(&D.ring_lock);
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS && b < 0; i++) {
+        if (r->state[i] == BUF_FREE) {
+            b = i;
+        }
+    }
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS && b < 0; i++) {
+        if (r->state[i] == BUF_READY) {     /* published but not shown yet: superseded */
+            b = i;
+        }
+    }
+    if (b >= 0) {
+        r->state[b] = BUF_WRITING;
+        if (r->latest == b) {
+            r->latest = -1;
+        }
+    }
+    qemu_mutex_unlock(&D.ring_lock);
+    return b;
+}
+
+static void sc_release(void *sink, QemuD3DScanoutRing *pub, int b)
+{
+    D3DRing *r = container_of(pub, D3DRing, pub);
+
+    qemu_mutex_lock(&D.ring_lock);
+    if (b >= 0 && b < QEMU_D3D_SCANOUT_BUFFERS && r->state[b] == BUF_WRITING) {
+        r->state[b] = BUF_FREE;
+    }
+    qemu_mutex_unlock(&D.ring_lock);
+}
+
+static void sc_publish(void *sink, QemuD3DScanoutRing *pub, int b, uint64_t value,
+                       int x, int y, int w, int h, int64_t frame_ns)
+{
+    D3DRing *r = container_of(pub, D3DRing, pub);
+    int i;
+
+    if (b < 0 || b >= QEMU_D3D_SCANOUT_BUFFERS) {
+        return;
+    }
+    qemu_mutex_lock(&D.ring_lock);
+    for (i = 0; i < QEMU_D3D_SCANOUT_BUFFERS; i++) {
+        if (i != b && r->state[i] == BUF_READY) {
+            r->state[i] = BUF_FREE;
+        }
+    }
+    r->state[b] = BUF_READY;
+    r->value[b] = value;
+    r->t_publish[b] = qpc_now();
+    r->frame_ns[b] = frame_ns;
+    r->latest = b;
+    qemu_mutex_unlock(&D.ring_lock);
+    SetEvent(D.wake);
+}
+
+static void sc_disable(void *sink)
+{
+    qemu_mutex_lock(&D.ring_lock);
+    if (D.ring) {
+        D.ring->latest = -1;
+    }
+    qemu_mutex_unlock(&D.ring_lock);
+    d3d_wake(PEND_DISABLE);
+}
+
+static const QemuD3DScanoutOps d3d_scanout_ops = {
+    .ring_create = sc_ring_create,
+    .ring_destroy = sc_ring_destroy,
+    .acquire = sc_acquire,
+    .release = sc_release,
+    .publish = sc_publish,
+    .disable = sc_disable,
+};
+
+/* ------------------------------------------------------------------------------------------ */
+/* window thread: Direct3D */
+
+static const char d3d_hlsl[] =
+    "struct V { float4 pos : SV_Position; float2 uv : TEXCOORD0; };\n"
+    "V vs_main(uint id : SV_VertexID) {\n"
+    "    V o;\n"
+    "    float2 uv = float2((id << 1) & 2, id & 2);\n"
+    "    o.pos = float4(uv * float2(2, -2) + float2(-1, 1), 0, 1);\n"
+    "    o.uv = uv;\n"
+    "    return o;\n"
+    "}\n"
+    "Texture2D tex : register(t0);\n"
+    "SamplerState smp : register(s0);\n"
+    "float4 ps_main(V i) : SV_Target {\n"
+    "    return float4(tex.Sample(smp, i.uv).rgb, 1);\n"
+    "}\n";
+
+static bool d3d_compile_shaders(void)
+{
+    pD3DCompile compile;
+    HMODULE m = LoadLibraryW(L"d3dcompiler_47.dll");
+    ID3DBlob *vsb = NULL, *psb = NULL, *err = NULL;
+    HRESULT hr;
+    bool ok = false;
+
+    compile = m ? (pD3DCompile)GetProcAddress(m, "D3DCompile") : NULL;
+    if (!compile) {
+        error_report("d3d11: d3dcompiler_47.dll not available");
+        return false;
+    }
+    hr = compile(d3d_hlsl, sizeof(d3d_hlsl) - 1, "d3d11.c", NULL, NULL, "vs_main", "vs_4_0", 0,
+                 0, &vsb, &err);
+    if (SUCCEEDED(hr)) {
+        hr = compile(d3d_hlsl, sizeof(d3d_hlsl) - 1, "d3d11.c", NULL, NULL, "ps_main", "ps_4_0",
+                     0, 0, &psb, &err);
+    }
+    if (FAILED(hr)) {
+        error_report("d3d11: shader compilation failed: %s",
+                     err ? (char *)ID3D10Blob_GetBufferPointer(err) : "?");
+        goto out;
+    }
+    hr = ID3D11Device_CreateVertexShader(D.dev, ID3D10Blob_GetBufferPointer(vsb),
+                                         ID3D10Blob_GetBufferSize(vsb), NULL, &D.vs);
+    if (SUCCEEDED(hr)) {
+        hr = ID3D11Device_CreatePixelShader(D.dev, ID3D10Blob_GetBufferPointer(psb),
+                                            ID3D10Blob_GetBufferSize(psb), NULL, &D.ps);
+    }
+    ok = SUCCEEDED(hr);
+out:
+    if (vsb) {
+        ID3D10Blob_Release(vsb);
+    }
+    if (psb) {
+        ID3D10Blob_Release(psb);
+    }
+    if (err) {
+        ID3D10Blob_Release(err);
+    }
+    return ok;
+}
+
+static bool d3d_create_device(void)
+{
+    D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0,
+                                   D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0 };
+    D3D11_SAMPLER_DESC sd = {
+        .Filter = D3D11_FILTER_MIN_MAG_MIP_POINT,
+        .AddressU = D3D11_TEXTURE_ADDRESS_CLAMP, .AddressV = D3D11_TEXTURE_ADDRESS_CLAMP,
+        .AddressW = D3D11_TEXTURE_ADDRESS_CLAMP, .MaxLOD = D3D11_FLOAT32_MAX,
+    };
+    UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+    IDXGIDevice *xdev = NULL;
+    IDXGIAdapter *adapter = NULL;
+    DXGI_ADAPTER_DESC ad;
+    HRESULT hr;
+
+    if (getenv("QEMU_D3D11_DEBUG")) {
+        flags |= D3D11_CREATE_DEVICE_DEBUG;
+    }
+    hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, levels,
+                           ARRAY_SIZE(levels), D3D11_SDK_VERSION, &D.dev, NULL, &D.ctx);
+    if (FAILED(hr)) {
+        error_report("d3d11: D3D11CreateDevice failed: 0x%lx", hr);
+        return false;
+    }
+    if (SUCCEEDED(ID3D11Device_QueryInterface(D.dev, &IID_IDXGIDevice, (void **)&xdev)) &&
+        SUCCEEDED(IDXGIDevice_GetAdapter(xdev, &adapter)) &&
+        SUCCEEDED(IDXGIAdapter_GetDesc(adapter, &ad))) {
+        D.luid = ad.AdapterLuid;
+        info_report("d3d11: %ls", ad.Description);
+    }
+    if (adapter) {
+        IDXGIAdapter_Release(adapter);
+    }
+    if (xdev) {
+        IDXGIDevice_Release(xdev);
+    }
+    /* D3D11.3/11.4 (fences): needed for zero-copy scanout only */
+    if (FAILED(ID3D11Device_QueryInterface(D.dev, &IID_ID3D11Device5, (void **)&D.dev5))) {
+        D.dev5 = NULL;
+    }
+    if (FAILED(ID3D11DeviceContext_QueryInterface(D.ctx, &IID_ID3D11DeviceContext4,
+                                                  (void **)&D.ctx4))) {
+        D.ctx4 = NULL;
+    }
+    if (D.dev5 && D.ctx4) {
+        if (FAILED(ID3D11Device5_CreateFence(D.dev5, 0, D3D11_FENCE_FLAG_NONE, &IID_ID3D11Fence,
+                                             (void **)&D.rel_fence))) {
+            D.rel_fence = NULL;
+        }
+    }
+    if (!D.rel_fence) {
+        warn_report("d3d11: no D3D11 fences: zero-copy scanout is not available");
+        if (D.dev5) {
+            ID3D11Device5_Release(D.dev5);
+            D.dev5 = NULL;
+        }
+    }
+    D.rel_event = CreateEventW(NULL, FALSE, FALSE, NULL);
+    if (!d3d_compile_shaders()) {
+        return false;
+    }
+    ID3D11Device_CreateSamplerState(D.dev, &sd, &D.smp_point);
+    sd.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+    ID3D11Device_CreateSamplerState(D.dev, &sd, &D.smp_linear);
+    return true;
+}
+
+/*
+ * Embedded (parent=): a composition swap chain shown by a DirectComposition visual on our
+ * window. CreateSwapChainForHwnd refuses flip-model swap chains for a child window of another
+ * process; DirectComposition takes such a window (and a top-level one after DETACH).
+ */
+static HRESULT d3d_create_composition_swapchain(IDXGIFactory2 *f2, IDXGIDevice *xdev,
+                                                DXGI_SWAP_CHAIN_DESC1 *desc,
+                                                IDXGISwapChain1 **sc1)
+{
+    HRESULT (WINAPI *create)(IDXGIDevice *, REFIID, void **);
+    HMODULE m;
+    RECT rc;
+    HRESULT hr;
+
+    if (!D.dcomp) {
+        m = LoadLibraryW(L"dcomp.dll");
+        create = m ? (void *)GetProcAddress(m, "DCompositionCreateDevice") : NULL;
+        if (!create) {
+            error_report("d3d11: dcomp.dll not available");
+            return E_NOINTERFACE;
+        }
+        hr = create(xdev, &d3d_iid_dcomp_device, (void **)&D.dcomp);
+        if (FAILED(hr)) {
+            error_report("d3d11: DCompositionCreateDevice failed: 0x%lx", hr);
+            D.dcomp = NULL;
+            return hr;
+        }
+    }
+    GetClientRect(D.hwnd, &rc);
+    desc->Width = MAX(rc.right - rc.left, 1);
+    desc->Height = MAX(rc.bottom - rc.top, 1);
+    desc->Scaling = DXGI_SCALING_STRETCH;  /* the only one composition takes; buffers = client */
+    hr = IDXGIFactory2_CreateSwapChainForComposition(f2, (IUnknown *)D.dev, desc, NULL, sc1);
+    if (FAILED(hr)) {
+        error_report("d3d11: CreateSwapChainForComposition failed: 0x%lx", hr);
+        return hr;
+    }
+    hr = D.dcomp->lpVtbl->CreateTargetForHwnd(D.dcomp, D.hwnd, TRUE, &D.dc_target);
+    if (SUCCEEDED(hr)) {
+        hr = D.dcomp->lpVtbl->CreateVisual(D.dcomp, &D.dc_visual);
+    }
+    if (SUCCEEDED(hr)) {
+        hr = D.dc_visual->lpVtbl->SetContent(D.dc_visual, (IUnknown *)*sc1);
+    }
+    if (SUCCEEDED(hr)) {
+        hr = D.dc_target->lpVtbl->SetRoot(D.dc_target, D.dc_visual);
+    }
+    if (SUCCEEDED(hr)) {
+        hr = D.dcomp->lpVtbl->Commit(D.dcomp);
+    }
+    if (FAILED(hr)) {
+        error_report("d3d11: DirectComposition target for the window failed: 0x%lx", hr);
+        if (D.dc_visual) {
+            D.dc_visual->lpVtbl->Release(D.dc_visual);
+            D.dc_visual = NULL;
+        }
+        if (D.dc_target) {
+            D.dc_target->lpVtbl->Release(D.dc_target);
+            D.dc_target = NULL;
+        }
+        IDXGISwapChain1_Release(*sc1);
+        *sc1 = NULL;
+    }
+    return hr;
+}
+
+static bool d3d_create_swapchain(void)
+{
+    DXGI_SWAP_CHAIN_DESC1 desc = {
+        .Format = DXGI_FORMAT_B8G8R8A8_UNORM, .SampleDesc = { 1, 0 },
+        .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, .BufferCount = 2,
+        .Scaling = DXGI_SCALING_NONE, .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
+        .AlphaMode = DXGI_ALPHA_MODE_IGNORE,
+        .Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT,
+    };
+    IDXGIDevice *xdev = NULL;
+    IDXGIAdapter *adapter = NULL;
+    IDXGIFactory2 *f2 = NULL;
+    IDXGIFactory5 *f5 = NULL;
+    IDXGISwapChain1 *sc1 = NULL;
+    BOOL tearing = FALSE;
+    HRESULT hr;
+
+    if (getenv("QEMU_D3D11_NO_SWAPCHAIN")) {
+        /*
+         * Tests on a window station without desktop composition (a service session), where
+         * no swap chain can be made: run the window, input and embedding, present nothing.
+         */
+        return true;
+    }
+    ID3D11Device_QueryInterface(D.dev, &IID_IDXGIDevice, (void **)&xdev);
+    IDXGIDevice_GetAdapter(xdev, &adapter);
+    hr = IDXGIAdapter_GetParent(adapter, &IID_IDXGIFactory2, (void **)&f2);
+    if (FAILED(hr)) {
+        error_report("d3d11: no IDXGIFactory2: 0x%lx", hr);
+        goto out;
+    }
+    if (SUCCEEDED(IDXGIFactory2_QueryInterface(f2, &IID_IDXGIFactory5, (void **)&f5))) {
+        if (FAILED(IDXGIFactory5_CheckFeatureSupport(f5, DXGI_FEATURE_PRESENT_ALLOW_TEARING,
+                                                     &tearing, sizeof(tearing)))) {
+            tearing = FALSE;
+        }
+        IDXGIFactory5_Release(f5);
+    }
+    if (D.embed) {
+        /* composition swap chains take no ALLOW_TEARING: vsync=off presents untorn */
+        tearing = FALSE;
+    }
+    D.tearing = tearing;
+    if (tearing) {
+        desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
+    }
+    if (D.embed) {
+        if (FAILED(d3d_create_composition_swapchain(f2, xdev, &desc, &sc1))) {
+            goto out;
+        }
+    } else {
+        hr = IDXGIFactory2_CreateSwapChainForHwnd(f2, (IUnknown *)D.dev, D.hwnd, &desc, NULL,
+                                                  NULL, &sc1);
+        if (FAILED(hr)) {
+            error_report("d3d11: CreateSwapChainForHwnd failed: 0x%lx", hr);
+            goto out;
+        }
+        IDXGIFactory2_MakeWindowAssociation(f2, D.hwnd, DXGI_MWA_NO_ALT_ENTER |
+                                            DXGI_MWA_NO_WINDOW_CHANGES);
+    }
+    hr = IDXGISwapChain1_QueryInterface(sc1, &IID_IDXGISwapChain2, (void **)&D.swap);
+    IDXGISwapChain1_Release(sc1);
+    if (FAILED(hr)) {
+        error_report("d3d11: no IDXGISwapChain2: 0x%lx", hr);
+        goto out;
+    }
+    IDXGISwapChain2_SetMaximumFrameLatency(D.swap, 1);
+    D.waitable = IDXGISwapChain2_GetFrameLatencyWaitableObject(D.swap);
+out:
+    if (f2) {
+        IDXGIFactory2_Release(f2);
+    }
+    if (adapter) {
+        IDXGIAdapter_Release(adapter);
+    }
+    if (xdev) {
+        IDXGIDevice_Release(xdev);
+    }
+    return D.swap != NULL;
+}
+
+static void d3d_resize_buffers(void)
+{
+    RECT rc;
+    int w, h;
+    HRESULT hr;
+
+    GetClientRect(D.hwnd, &rc);
+    w = rc.right - rc.left;
+    h = rc.bottom - rc.top;
+    if (w <= 0 || h <= 0 || !D.swap) {
+        return;
+    }
+    if (w == D.cw && h == D.ch && D.rtv) {
+        return;
+    }
+    if (D.rtv) {
+        ID3D11DeviceContext_OMSetRenderTargets(D.ctx, 0, NULL, NULL);
+        ID3D11RenderTargetView_Release(D.rtv);
+        D.rtv = NULL;
+    }
+    hr = IDXGISwapChain2_ResizeBuffers(D.swap, 0, w, h, DXGI_FORMAT_UNKNOWN,
+                                       DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT |
+                                       (D.tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0));
+    if (FAILED(hr)) {
+        error_report("d3d11: ResizeBuffers %dx%d failed: 0x%lx", w, h, hr);
+        return;
+    }
+    {
+        ID3D11Texture2D *bb = NULL;
+        if (SUCCEEDED(IDXGISwapChain2_GetBuffer(D.swap, 0, &IID_ID3D11Texture2D, (void **)&bb))) {
+            ID3D11Device_CreateRenderTargetView(D.dev, (ID3D11Resource *)bb, NULL, &D.rtv);
+            ID3D11Texture2D_Release(bb);
+        }
+    }
+    D.cw = w;
+    D.ch = h;
+}
+
+/* the window is gone: drop its swap chain (the device and textures stay) */
+static void d3d_release_swapchain(void)
+{
+    if (D.rtv) {
+        ID3D11DeviceContext_OMSetRenderTargets(D.ctx, 0, NULL, NULL);
+        ID3D11RenderTargetView_Release(D.rtv);
+        D.rtv = NULL;
+    }
+    if (D.dc_visual) {
+        D.dc_visual->lpVtbl->Release(D.dc_visual);
+        D.dc_visual = NULL;
+    }
+    if (D.dc_target) {
+        D.dc_target->lpVtbl->Release(D.dc_target);
+        D.dc_target = NULL;
+    }
+    if (D.swap) {
+        ID3D11DeviceContext_ClearState(D.ctx);
+        ID3D11DeviceContext_Flush(D.ctx);
+        IDXGISwapChain2_Release(D.swap);
+        D.swap = NULL;
+    }
+    if (D.dcomp) {
+        D.dcomp->lpVtbl->Commit(D.dcomp);
+    }
+    if (D.waitable) {
+        CloseHandle(D.waitable);
+        D.waitable = NULL;
+    }
+    D.cw = D.ch = 0;
+    D.slot_free = false;
+    D.st_pend_n = 0;
+}
+
+static bool d3d_make_texture(int w, int h, DXGI_FORMAT fmt, ID3D11Texture2D **tex,
+                             ID3D11ShaderResourceView **srv)
+{
+    D3D11_TEXTURE2D_DESC td = {
+        .Width = w, .Height = h, .MipLevels = 1, .ArraySize = 1, .Format = fmt,
+        .SampleDesc = { 1, 0 }, .Usage = D3D11_USAGE_DEFAULT,
+        .BindFlags = D3D11_BIND_SHADER_RESOURCE,
+    };
+
+    if (*srv) {
+        ID3D11ShaderResourceView_Release(*srv);
+        *srv = NULL;
+    }
+    if (*tex) {
+        ID3D11Texture2D_Release(*tex);
+        *tex = NULL;
+    }
+    if (FAILED(ID3D11Device_CreateTexture2D(D.dev, &td, NULL, tex))) {
+        return false;
+    }
+    if (FAILED(ID3D11Device_CreateShaderResourceView(D.dev, (ID3D11Resource *)*tex, NULL,
+                                                     srv))) {
+        ID3D11Texture2D_Release(*tex);
+        *tex = NULL;
+        return false;
+    }
+    return true;
+}
+
+/* where the picture goes in the client area, and how it is scaled */
+static void d3d_compute_view(int tw, int th, bool *linear)
+{
+    int cw = D.cw, ch = D.ch, vw, vh;
+
+    *linear = false;
+    if (tw <= 0 || th <= 0 || cw <= 0 || ch <= 0) {
+        SetRect(&D.view, 0, 0, cw, ch);
+        D.view_scale = 1;
+        return;
+    }
+    if (tw <= cw && th <= ch && D.integer_scale) {
+        int s = MIN(cw / tw, ch / th);
+        vw = tw * s;
+        vh = th * s;
+        D.view_scale = s;
+    } else {
+        /* scale to fit, keeping the aspect ratio (smooth); 1:1 if it already fits exactly */
+        double s = MIN((double)cw / tw, (double)ch / th);
+        vw = MAX(1, (int)(tw * s + 0.5));
+        vh = MAX(1, (int)(th * s + 0.5));
+        if (vw > cw) {
+            vw = cw;
+        }
+        if (vh > ch) {
+            vh = ch;
+        }
+        D.view_scale = s;
+        *linear = vw != tw || vh != th;
+    }
+    D.view.left = (cw - vw) / 2;
+    D.view.top = (ch - vh) / 2;
+    D.view.right = D.view.left + vw;
+    D.view.bottom = D.view.top + vh;
+}
+
+static void d3d_stats_frame_statistics(void)
+{
+    DXGI_FRAME_STATISTICS fs;
+    int i, j;
+
+    if (!D.st_pend_n || !D.swap || FAILED(IDXGISwapChain2_GetFrameStatistics(D.swap, &fs))) {
+        return;
+    }
+    for (i = 0, j = 0; i < D.st_pend_n; i++) {
+        if (D.st_pend[i].pc == fs.PresentCount) {
+            double ms = qpc_ms(fs.SyncQPCTime.QuadPart - D.st_pend[i].t);
+            if (ms >= 0 && ms < 1000) {
+                D.st_disp_sum += ms;
+                D.st_disp_max = MAX(D.st_disp_max, ms);
+                D.st_disp_n++;
+            }
+        } else if (D.st_pend[i].pc > fs.PresentCount) {
+            D.st_pend[j++] = D.st_pend[i];
+        }
+    }
+    D.st_pend_n = j;
+}
+
+static void d3d_stats_report(void)
+{
+    int64_t now = qpc_now();
+    double secs = qpc_ms(now - D.st_t0) / 1000;
+    FILETIME c, x, k, u;
+    static uint64_t last_cpu;
+    uint64_t cpu = 0;
+
+    if (secs < 2) {
+        return;
+    }
+    if (GetThreadTimes(GetCurrentThread(), &c, &x, &k, &u)) {
+        cpu = ((uint64_t)k.dwHighDateTime << 32 | k.dwLowDateTime) +
+              ((uint64_t)u.dwHighDateTime << 32 | u.dwLowDateTime);
+    }
+    fprintf(stderr, "d3d11: %.1f presents/s (%d scanout frames, %d surface); guest present->"
+            "Present %.2f ms avg %.2f max; publish->Present %.2f ms avg %.2f max; "
+            "publish->screen %.2f ms avg %.2f max (%d samples); "
+            "window thread cpu %.2f ms/s, upload %.2f ms/s\n",
+            D.st_presents / secs, D.st_frames, D.st_surface_frames,
+            D.st_glat_n ? D.st_glat_sum / D.st_glat_n : 0.0, D.st_glat_max,
+            D.st_lat_n ? D.st_lat_sum / D.st_lat_n : 0.0, D.st_lat_max,
+            D.st_disp_n ? D.st_disp_sum / D.st_disp_n : 0.0, D.st_disp_max, D.st_disp_n,
+            last_cpu ? (cpu - last_cpu) / 1e4 / secs : 0.0, D.st_upload_ms / secs);
+    last_cpu = cpu;
+    D.st_t0 = now;
+    D.st_presents = D.st_frames = D.st_surface_frames = 0;
+    D.st_lat_sum = D.st_lat_max = D.st_disp_sum = D.st_disp_max = 0;
+    D.st_lat_n = D.st_disp_n = 0;
+    D.st_glat_sum = D.st_glat_max = 0;
+    D.st_glat_n = 0;
+    D.st_upload_ms = 0;
+}
+
+/* debugging: write the back buffer (what is about to be presented) to a PPM file */
+static void d3d_snapshot(void)
+{
+    const char *path = getenv("QEMU_D3D11_SNAP");
+    ID3D11Texture2D *bb = NULL, *stg = NULL;
+    D3D11_TEXTURE2D_DESC td;
+    D3D11_MAPPED_SUBRESOURCE map;
+    FILE *f;
+    int x, y;
+
+    D.snap_req = false;
+    if (!path || FAILED(IDXGISwapChain2_GetBuffer(D.swap, 0, &IID_ID3D11Texture2D,
+                                                  (void **)&bb))) {
+        return;
+    }
+    ID3D11Texture2D_GetDesc(bb, &td);
+    td.Usage = D3D11_USAGE_STAGING;
+    td.BindFlags = 0;
+    td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+    td.MiscFlags = 0;
+    if (SUCCEEDED(ID3D11Device_CreateTexture2D(D.dev, &td, NULL, &stg))) {
+        ID3D11DeviceContext_CopyResource(D.ctx, (ID3D11Resource *)stg, (ID3D11Resource *)bb);
+        if (SUCCEEDED(ID3D11DeviceContext_Map(D.ctx, (ID3D11Resource *)stg, 0, D3D11_MAP_READ, 0,
+                                              &map))) {
+            f = fopen(path, "wb");
+            if (f) {
+                fprintf(f, "P6\n%u %u\n255\n", td.Width, td.Height);
+                for (y = 0; y < (int)td.Height; y++) {
+                    const uint8_t *row = (const uint8_t *)map.pData + (size_t)y * map.RowPitch;
+                    for (x = 0; x < (int)td.Width; x++) {
+                        uint8_t px[3] = { row[x * 4 + 2], row[x * 4 + 1], row[x * 4] };
+                        fwrite(px, 1, 3, f);
+                    }
+                }
+                fclose(f);
+                info_report("d3d11: snapshot %ux%u (%s, view %ld,%ld %ldx%ld) -> %s", td.Width,
+                            td.Height, D.scanout_on ? "scanout frame" : "surface",
+                            D.view.left, D.view.top, D.view.right - D.view.left,
+                            D.view.bottom - D.view.top, path);
+            }
+            ID3D11DeviceContext_Unmap(D.ctx, (ID3D11Resource *)stg, 0);
+        }
+        ID3D11Texture2D_Release(stg);
+    }
+    ID3D11Texture2D_Release(bb);
+    if (D.hcursor) {
+        /* and the host cursor as Windows has it: <file>.cursor.ppm over magenta */
+        ICONINFO ii = { 0 };
+        BITMAP bm;
+        if (GetIconInfo(D.hcursor, &ii) && ii.hbmColor &&
+            GetObject(ii.hbmColor, sizeof(bm), &bm)) {
+            BITMAPINFOHEADER bi = { .biSize = sizeof(bi), .biWidth = bm.bmWidth,
+                                    .biHeight = -bm.bmHeight, .biPlanes = 1,
+                                    .biBitCount = 32, .biCompression = BI_RGB };
+            g_autofree uint32_t *px = g_new(uint32_t, (size_t)bm.bmWidth * bm.bmHeight);
+            g_autofree char *cpath = g_strdup_printf("%s.cursor.ppm", path);
+            HDC dc = GetDC(NULL);
+            if (GetDIBits(dc, ii.hbmColor, 0, bm.bmHeight, px, (BITMAPINFO *)&bi,
+                          DIB_RGB_COLORS) && (f = fopen(cpath, "wb"))) {
+                fprintf(f, "P6\n%ld %ld\n255\n", bm.bmWidth, bm.bmHeight);
+                for (x = 0; x < bm.bmWidth * bm.bmHeight; x++) {
+                    uint32_t p = px[x], a = p >> 24;
+                    uint8_t c[3] = {
+                        (((p >> 16) & 0xff) * a + 255 * (255 - a)) / 255,
+                        (((p >> 8) & 0xff) * a) / 255,
+                        ((p & 0xff) * a + 255 * (255 - a)) / 255,
+                    };
+                    fwrite(c, 1, 3, f);
+                }
+                fclose(f);
+                info_report("d3d11: snapshot of the cursor: %ldx%ld, hot spot %lu,%lu, "
+                            "guest image %ux%u at scale %d -> %s", bm.bmWidth, bm.bmHeight,
+                            ii.xHotspot, ii.yHotspot, D.cursor ? D.cursor->width : 0,
+                            D.cursor ? D.cursor->height : 0, D.cursor_scale, cpath);
+            }
+            ReleaseDC(NULL, dc);
+        }
+        if (ii.hbmColor) {
+            DeleteObject(ii.hbmColor);
+        }
+        if (ii.hbmMask) {
+            DeleteObject(ii.hbmMask);
+        }
+    }
+}
+
+static void d3d_render(int64_t t_publish)
+{
+    ID3D11ShaderResourceView *srv;
+    float black[4] = { 0, 0, 0, 1 };
+    D3D11_VIEWPORT vp;
+    bool linear;
+    int tw, th;
+    HRESULT hr;
+
+    if (!D.rtv) {
+        return;
+    }
+    if (D.scanout_on && D.frame_srv) {
+        srv = D.frame_srv;
+        tw = D.fw;
+        th = D.fh;
+    } else {
+        srv = D.surf_srv;
+        tw = D.sw;
+        th = D.sh;
+    }
+    d3d_compute_view(tw, th, &linear);
+    ID3D11DeviceContext_ClearRenderTargetView(D.ctx, D.rtv, black);
+    if (srv) {
+        vp.TopLeftX = D.view.left;
+        vp.TopLeftY = D.view.top;
+        vp.Width = D.view.right - D.view.left;
+        vp.Height = D.view.bottom - D.view.top;
+        vp.MinDepth = 0;
+        vp.MaxDepth = 1;
+        ID3D11DeviceContext_OMSetRenderTargets(D.ctx, 1, &D.rtv, NULL);
+        ID3D11DeviceContext_RSSetViewports(D.ctx, 1, &vp);
+        ID3D11DeviceContext_IASetPrimitiveTopology(D.ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+        ID3D11DeviceContext_IASetInputLayout(D.ctx, NULL);
+        ID3D11DeviceContext_VSSetShader(D.ctx, D.vs, NULL, 0);
+        ID3D11DeviceContext_PSSetShader(D.ctx, D.ps, NULL, 0);
+        ID3D11DeviceContext_PSSetShaderResources(D.ctx, 0, 1, &srv);
+        ID3D11DeviceContext_PSSetSamplers(D.ctx, 0, 1, linear ? &D.smp_linear : &D.smp_point);
+        ID3D11DeviceContext_Draw(D.ctx, 3, 0);
+    }
+    if (D.snap_req) {
+        d3d_snapshot();
+    }
+    if (t_publish && D.stats) {
+        double ms = qpc_ms(qpc_now() - t_publish);
+        D.st_lat_sum += ms;
+        D.st_lat_max = MAX(D.st_lat_max, ms);
+        D.st_lat_n++;
+        if (D.cur_frame_ns) {
+            ms = (qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - D.cur_frame_ns) / 1e6;
+            D.st_glat_sum += ms;
+            D.st_glat_max = MAX(D.st_glat_max, ms);
+            D.st_glat_n++;
+        }
+    }
+    hr = IDXGISwapChain2_Present(D.swap, D.vsync ? 1 : 0,
+                                 !D.vsync && D.tearing ?
+                                 DXGI_PRESENT_ALLOW_TEARING : 0);
+    if (FAILED(hr) && hr != DXGI_ERROR_WAS_STILL_DRAWING) {
+        static int logged;
+        if (logged++ < 5) {
+            error_report("d3d11: Present failed: 0x%lx", hr);
+        }
+    }
+    {
+        ID3D11ShaderResourceView *none = NULL;
+        ID3D11DeviceContext_PSSetShaderResources(D.ctx, 0, 1, &none);
+    }
+    D.st_presents++;
+    if (D.stats) {
+        UINT pc = 0;
+        if (t_publish && SUCCEEDED(IDXGISwapChain2_GetLastPresentCount(D.swap, &pc)) &&
+            D.st_pend_n < ARRAY_SIZE(D.st_pend)) {
+            D.st_pend[D.st_pend_n].pc = pc;
+            D.st_pend[D.st_pend_n].t = t_publish;
+            D.st_pend_n++;
+        }
+    }
+}
+
+/* upload the dirty part of the console surface (window thread; D.lock keeps it alive) */
+static void d3d_upload_surface(bool full)
+{
+    DisplaySurface *s;
+    bool changed = false;
+    int x0, y0, x1, y1;
+    int64_t t0 = D.stats ? qpc_now() : 0;
+
+    qemu_mutex_lock(&D.lock);
+    s = D.surface;
+    if (full) {
+        D.pending |= PEND_DIRTY;
+        D.dirty_x0 = D.dirty_y0 = 0;
+        D.dirty_x1 = D.dirty_y1 = INT_MAX;
+    }
+    if (!s || !(D.pending & PEND_DIRTY) || !D.surf_tex || (D.scanout_on && !full) ||
+        surface_width(s) != D.sw || surface_height(s) != D.sh) {
+        qemu_mutex_unlock(&D.lock);
+        return;
+    }
+    D.pending &= ~PEND_DIRTY;
+    x0 = MAX(D.dirty_x0, 0);
+    y0 = MAX(D.dirty_y0, 0);
+    x1 = MIN(D.dirty_x1, D.sw);
+    y1 = MIN(D.dirty_y1, D.sh);
+    if (full || !D.shadow) {
+        /* (re)start the shadow copy of what the texture holds */
+        g_free(D.shadow);
+        D.shadow = g_malloc((size_t)D.sw * D.sh * 4);
+        D.shadow_valid = false;
+    }
+    if (x1 > x0 && y1 > y0) {
+        const uint8_t *data = surface_data(s);
+        size_t stride = surface_stride(s), row = (size_t)(x1 - x0) * 4;
+        int y = y0, run;
+
+        /*
+         * Under WHPX every refresh reports the whole framebuffer dirty (no dirty logging):
+         * upload only the rows that really changed since the last upload.
+         */
+        while (y < y1) {
+            if (D.shadow_valid &&
+                !memcmp(data + y * stride + (size_t)x0 * 4,
+                        D.shadow + ((size_t)y * D.sw + x0) * 4, row)) {
+                y++;
+                continue;
+            }
+            for (run = y + 1; run < y1; run++) {
+                if (D.shadow_valid &&
+                    !memcmp(data + run * stride + (size_t)x0 * 4,
+                            D.shadow + ((size_t)run * D.sw + x0) * 4, row)) {
+                    break;
+                }
+            }
+            {
+                D3D11_BOX box = { x0, y, 0, x1, run, 1 };
+                int r;
+                ID3D11DeviceContext_UpdateSubresource(D.ctx, (ID3D11Resource *)D.surf_tex, 0,
+                                                      &box, data + y * stride + (size_t)x0 * 4,
+                                                      stride, 0);
+                for (r = y; r < run; r++) {
+                    memcpy(D.shadow + ((size_t)r * D.sw + x0) * 4,
+                           data + r * stride + (size_t)x0 * 4, row);
+                }
+                changed = true;
+            }
+            y = run;
+        }
+        if (x0 == 0 && y0 == 0 && x1 == D.sw && y1 == D.sh) {
+            D.shadow_valid = true;
+        }
+    }
+    qemu_mutex_unlock(&D.lock);
+    if (t0) {
+        D.st_upload_ms += qpc_ms(qpc_now() - t0);
+    }
+    if (!D.scanout_on && changed) {
+        D.need_render = true;
+        D.st_surface_frames++;
+    }
+}
+
+/* take the newest published ring frame, if any (window thread) */
+static int64_t d3d_take_frame(void)
+{
+    D3DRing *r;
+    int b = -1;
+    uint64_t value = 0;
+    int64_t t = 0;
+    bool fits = false;
+
+    qemu_mutex_lock(&D.ring_lock);
+    r = D.ring;
+    if (r && !r->dead && r->latest >= 0 && r->state[r->latest] == BUF_READY) {
+        b = r->latest;
+        r->latest = -1;
+        fits = (int)r->pub.width == D.sw && (int)r->pub.height == D.sh;
+        r->state[b] = fits ? BUF_READING : BUF_FREE;
+        value = r->value[b];
+        t = r->t_publish[b];
+        D.cur_frame_ns = r->frame_ns[b];
+        if (fits) {
+            qatomic_inc(&r->refs);
+        }
+    }
+    qemu_mutex_unlock(&D.ring_lock);
+    if (b >= 0 && !fits) {
+        D3DEvent e = { .type = DE_HWUPDATE };
+        d3d_post(&e);
+    }
+    if (b < 0 || !fits) {
+        return 0;
+    }
+    if (!D.frame_tex || D.fw != (int)r->pub.width || D.fh != (int)r->pub.height) {
+        if (!d3d_make_texture(r->pub.width, r->pub.height, DXGI_FORMAT_R8G8B8A8_UNORM,
+                              &D.frame_tex, &D.frame_srv)) {
+            error_report("d3d11: frame texture %ux%u failed", r->pub.width, r->pub.height);
+        }
+        D.fw = r->pub.width;
+        D.fh = r->pub.height;
+    }
+    if (value) {
+        ID3D11DeviceContext4_Wait(D.ctx4, r->fence, value);
+    }
+    if (D.frame_tex) {
+        ID3D11DeviceContext_CopyResource(D.ctx, (ID3D11Resource *)D.frame_tex,
+                                         (ID3D11Resource *)r->tex[b]);
+    }
+    D.rel_value++;
+    ID3D11DeviceContext4_Signal(D.ctx4, D.rel_fence, D.rel_value);
+    ID3D11Fence_SetEventOnCompletion(D.rel_fence, D.rel_value, D.rel_event);
+    qemu_mutex_lock(&D.ring_lock);
+    r->done[b] = D.rel_value;
+    qemu_mutex_unlock(&D.ring_lock);
+    ring_unref(r);
+    D.scanout_on = D.frame_tex != NULL;
+    D.need_render = true;
+    D.st_frames++;
+    return t;
+}
+
+/* give back ring buffers whose copy has finished; drop retired rings */
+static void d3d_release_buffers(void)
+{
+    uint64_t done;
+    guint i;
+    int b;
+
+    if (!D.rel_fence) {
+        return;
+    }
+    done = ID3D11Fence_GetCompletedValue(D.rel_fence);
+    qemu_mutex_lock(&D.ring_lock);
+    if (D.ring) {
+        bool busy = false;
+        for (b = 0; b < QEMU_D3D_SCANOUT_BUFFERS; b++) {
+            if (D.ring->state[b] == BUF_READING && D.ring->done[b] && D.ring->done[b] <= done) {
+                D.ring->state[b] = BUF_FREE;
+            }
+            busy |= D.ring->state[b] == BUF_READING;
+        }
+        if (D.ring->dead && !busy) {
+            /* the producer dropped it without making a new one */
+            ring_unref(D.ring);
+            D.ring = NULL;
+        }
+    }
+    for (i = 0; i < D.retired->len;) {
+        D3DRing *r = g_ptr_array_index(D.retired, i);
+        bool busy = false;
+        for (b = 0; b < QEMU_D3D_SCANOUT_BUFFERS; b++) {
+            if (r->state[b] == BUF_READING && (!r->done[b] || r->done[b] > done)) {
+                busy = true;
+            }
+        }
+        if (busy) {
+            i++;
+        } else {
+            g_ptr_array_remove_index_fast(D.retired, i);
+            ring_unref(r);
+        }
+    }
+    qemu_mutex_unlock(&D.ring_lock);
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* window thread: window management */
+
+static UINT d3d_dpi(void)
+{
+    return p_GetDpiForWindow ? p_GetDpiForWindow(D.hwnd) : 96;
+}
+
+/* frame sizes around a client area of this window (left, top, right, bottom) */
+static void d3d_frame_borders(RECT *b)
+{
+    RECT r = { 0, 0, 100, 100 };
+    DWORD style = GetWindowLongW(D.hwnd, GWL_STYLE);
+    DWORD exstyle = GetWindowLongW(D.hwnd, GWL_EXSTYLE);
+
+    if (p_AdjustWindowRectExForDpi) {
+        p_AdjustWindowRectExForDpi(&r, style, FALSE, exstyle, d3d_dpi());
+    } else {
+        AdjustWindowRectEx(&r, style, FALSE, exstyle);
+    }
+    b->left = -r.left;
+    b->top = -r.top;
+    b->right = r.right - 100;
+    b->bottom = r.bottom - 100;
+}
+
+/* the invisible resize borders of a window (window rect minus the visible frame) */
+static void d3d_invisible_borders(RECT *m)
+{
+    RECT wr, fr;
+
+    SetRect(m, 0, 0, 0, 0);
+    if (GetWindowRect(D.hwnd, &wr) &&
+        SUCCEEDED(DwmGetWindowAttribute(D.hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &fr, sizeof(fr)))) {
+        m->left = fr.left - wr.left;
+        m->top = fr.top - wr.top;
+        m->right = wr.right - fr.right;
+        m->bottom = wr.bottom - fr.bottom;
+    }
+}
+
+/*
+ * The guest mode changed (or full screen was left): show the picture 1:1, keeping the
+ * window's centre, moved as needed to stay on its monitor's work area; a picture bigger than
+ * the work area is scaled down to fit it, keeping the aspect ratio.
+ */
+static void d3d_window_fit(void)
+{
+    MONITORINFO mi = { .cbSize = sizeof(mi) };
+    RECT b, inv, wr, area;
+    int w = D.sw, h = D.sh, ww, wh, x, y, aw, ah;
+
+    if (!D.hwnd || D.parent || D.fullscreen || w <= 0 || h <= 0 || IsZoomed(D.hwnd) ||
+        IsIconic(D.hwnd)) {
+        return;                 /* (embedded: the parent places the window) */
+    }
+    d3d_frame_borders(&b);
+    d3d_invisible_borders(&inv);
+    GetMonitorInfoW(MonitorFromWindow(D.hwnd, MONITOR_DEFAULTTONEAREST), &mi);
+    area = mi.rcWork;
+    /* the visible frame has to fit the work area, the invisible borders may hang over */
+    aw = (area.right - area.left) - (b.left + b.right - inv.left - inv.right);
+    ah = (area.bottom - area.top) - (b.top + b.bottom - inv.top - inv.bottom);
+    if (w > aw || h > ah) {
+        double s = MIN((double)aw / w, (double)ah / h);
+        w = MAX(1, (int)(w * s));
+        h = MAX(1, (int)(h * s));
+    }
+    ww = w + b.left + b.right;
+    wh = h + b.top + b.bottom;
+    GetWindowRect(D.hwnd, &wr);
+    if (D.window_shown) {
+        x = (wr.left + wr.right) / 2 - ww / 2;
+        y = (wr.top + wr.bottom) / 2 - wh / 2;
+    } else {
+        /* first show: centre on the monitor's work area */
+        x = (area.left + area.right) / 2 - ww / 2;
+        y = (area.top + area.bottom) / 2 - wh / 2;
+    }
+    x = MAX(MIN(x, area.right + inv.right - ww), area.left - inv.left);
+    y = MAX(MIN(y, area.bottom + inv.bottom - wh), area.top - inv.top);
+    SetWindowPos(D.hwnd, NULL, x, y, ww, wh, SWP_NOZORDER | SWP_NOACTIVATE);
+}
+
+static void d3d_set_fullscreen(bool on)
+{
+    MONITORINFO mi = { .cbSize = sizeof(mi) };
+
+    if (on == D.fullscreen) {
+        return;
+    }
+    if (on) {
+        D.saved_placement.length = sizeof(D.saved_placement);
+        GetWindowPlacement(D.hwnd, &D.saved_placement);
+        GetMonitorInfoW(MonitorFromWindow(D.hwnd, MONITOR_DEFAULTTONEAREST), &mi);
+        D.fullscreen = true;
+        SetWindowLongW(D.hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
+        SetWindowPos(D.hwnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
+                     mi.rcMonitor.right - mi.rcMonitor.left,
+                     mi.rcMonitor.bottom - mi.rcMonitor.top,
+                     SWP_FRAMECHANGED | SWP_NOOWNERZORDER);
+    } else {
+        D.fullscreen = false;
+        SetWindowLongW(D.hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
+        SetWindowPlacement(D.hwnd, &D.saved_placement);
+        SetWindowPos(D.hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
+                     SWP_NOZORDER | SWP_NOOWNERZORDER);
+        d3d_window_fit();       /* the guest may have changed modes meanwhile */
+    }
+}
+
+static void d3d_set_title(void)
+{
+    char t[300];
+    wchar_t wt[300];
+
+    qemu_mutex_lock(&D.lock);
+    snprintf(t, sizeof(t), "%s%s", D.title,
+             D.grab ? " - Press Ctrl-Alt-G to exit grab" : "");
+    qemu_mutex_unlock(&D.lock);
+    MultiByteToWideChar(CP_UTF8, 0, t, -1, wt, ARRAY_SIZE(wt));
+    SetWindowTextW(D.hwnd, wt);
+}
+
+/* embedded: tell the parent (posted: the window thread never waits for the launcher) */
+static void d3d_notify(WPARAM event, LPARAM lp)
+{
+    if (D.parent && D.msg_notify) {
+        PostMessageW(D.parent, D.msg_notify, event, lp);
+    }
+}
+
+/* embedded: the guest picture size, once known and on every change */
+static void d3d_notify_size(bool force)
+{
+    if (D.parent && D.sw > 0 && D.sh > 0 && (force || D.sw != D.size_w || D.sh != D.size_h)) {
+        D.size_w = D.sw;
+        D.size_h = D.sh;
+        d3d_notify(EMBED_N_SIZE, MAKELPARAM(D.sw, D.sh));
+    }
+}
+
+/*
+ * Nothing to render while minimised or, embedded, while the child (or the parent, or the
+ * launcher's top-level window) is hidden or minimised; the main loop then refreshes slowly.
+ */
+static void d3d_update_paused(void)
+{
+    bool paused;
+
+    D.hidden = false;
+    if (D.parent && D.hwnd) {
+        /* we and all our ancestors are shown (not IsWindowVisible: it asks the desktop too) */
+        HWND w, root = GetAncestor(D.parent, GA_ROOT), desk = GetDesktopWindow();
+        for (w = D.hwnd; w && w != desk; w = GetAncestor(w, GA_PARENT)) {
+            if (!(GetWindowLongW(w, GWL_STYLE) & WS_VISIBLE)) {
+                D.hidden = true;
+            }
+        }
+        D.hidden |= D.parent_closed || (root && IsIconic(root));
+    }
+    paused = D.minimized || D.hidden;
+    if (paused != D.paused) {
+        D3DEvent e = { .type = DE_REFRESH, .down = paused && D.embed,
+                       .code = paused ? 500 : GUI_REFRESH_INTERVAL_DEFAULT };
+        if (D.stats) {
+            fprintf(stderr, "d3d11: %s\n", paused ? (D.hidden ? "hidden: rendering paused" :
+                                                     "minimised: rendering paused") :
+                    "rendering resumed");
+        }
+        D.paused = paused;
+        d3d_post(&e);
+        if (!paused) {
+            D.need_render = true;
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* window thread: pointer */
+
+static int d3d_cursor_scale(void)
+{
+    int s = (int)(D.view_scale + 0.5);
+    return MAX(1, MIN(s, 8));
+}
+
+static HCURSOR d3d_create_hcursor(QEMUCursor *c, int scale)
+{
+    int w = c->width * scale, h = c->height * scale;
+    BITMAPV5HEADER bi = {
+        .bV5Size = sizeof(bi), .bV5Width = w, .bV5Height = -h, .bV5Planes = 1,
+        .bV5BitCount = 32, .bV5Compression = BI_BITFIELDS, .bV5RedMask = 0x00ff0000,
+        .bV5GreenMask = 0x0000ff00, .bV5BlueMask = 0x000000ff, .bV5AlphaMask = 0xff000000,
+    };
+    ICONINFO ii = { .fIcon = FALSE, .xHotspot = c->hot_x * scale, .yHotspot = c->hot_y * scale };
+    int mask_stride = (w + 15) / 16 * 2;     /* monochrome rows are WORD aligned */
+    g_autofree uint8_t *mask = g_malloc0((size_t)mask_stride * h);
+    HCURSOR cursor = NULL;
+    uint32_t *bits;
+    int x, y;
+
+    ii.hbmColor = CreateDIBSection(NULL, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&bits,
+                                   NULL, 0);
+    if (ii.hbmColor) {
+        for (y = 0; y < h; y++) {
+            const uint32_t *src = c->data + (y / scale) * c->width;
+            for (x = 0; x < w; x++) {
+                uint32_t p = src[x / scale];
+                bits[y * w + x] = p;
+                /* AND mask: transparent where alpha is 0 (Windows uses the alpha otherwise) */
+                if (!(p >> 24)) {
+                    mask[y * mask_stride + x / 8] |= 0x80 >> (x % 8);
+                }
+            }
+        }
+        ii.hbmMask = CreateBitmap(w, h, 1, 1, mask);
+        if (ii.hbmMask) {
+            cursor = (HCURSOR)CreateIconIndirect(&ii);
+        }
+    }
+    if (ii.hbmColor) {
+        DeleteObject(ii.hbmColor);
+    }
+    if (ii.hbmMask) {
+        DeleteObject(ii.hbmMask);
+    }
+    return cursor;
+}
+
+/* the cursor for the client area */
+static HCURSOR d3d_client_cursor(void)
+{
+    static HCURSOR arrow;
+
+    if (!arrow) {
+        arrow = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
+    }
+    if (D.show_cursor) {
+        return arrow;
+    }
+    if (!D.wt_absolute) {
+        return D.grab ? NULL : arrow;
+    }
+    return D.wt_cursor_on ? D.hcursor : NULL;
+}
+
+static bool d3d_pointer_in_client(void)
+{
+    POINT p;
+    RECT rc;
+
+    if (!GetCursorPos(&p) || WindowFromPoint(p) != D.hwnd) {
+        return false;
+    }
+    ScreenToClient(D.hwnd, &p);
+    GetClientRect(D.hwnd, &rc);
+    return PtInRect(&rc, p);
+}
+
+static void d3d_apply_cursor(void)
+{
+    if (d3d_pointer_in_client()) {
+        SetCursor(d3d_client_cursor());
+    }
+}
+
+static void d3d_rebuild_cursor(void)
+{
+    HCURSOR old = D.hcursor;
+    int scale = d3d_cursor_scale();
+
+    D.hcursor = D.cursor ? d3d_create_hcursor(D.cursor, scale) : NULL;
+    if (D.cursor && scale != D.cursor_scale) {
+        info_report("d3d11: guest cursor %ux%u shown at %dx", D.cursor->width,
+                    D.cursor->height, scale);
+    }
+    D.cursor_scale = scale;
+    d3d_apply_cursor();
+    if (old) {
+        DestroyCursor(old);
+    }
+}
+
+static void d3d_update_kbd_grab(void)
+{
+    bool g = D.focus && (D.grab || D.fullscreen || (D.wt_absolute && D.mouse_inside));
+
+    if (g != D.kbd_grab) {
+        D.kbd_grab = g;
+        win32_kbd_set_grab(g);
+    }
+}
+
+static void d3d_set_raw_input(bool on)
+{
+    RAWINPUTDEVICE rid = { .usUsagePage = 0x01, .usUsage = 0x02,
+                           .dwFlags = on ? 0 : RIDEV_REMOVE, .hwndTarget = on ? D.hwnd : NULL };
+
+    if (on != D.raw_input && RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
+        D.raw_input = on;
+    }
+}
+
+static void d3d_set_grab(bool on)
+{
+    if (on == D.grab) {
+        return;
+    }
+    D.grab = on;
+    if (on) {
+        RECT rc;
+        GetClientRect(D.hwnd, &rc);
+        MapWindowPoints(D.hwnd, NULL, (POINT *)&rc, 2);
+        ClipCursor(&rc);
+    } else {
+        ClipCursor(NULL);
+    }
+    d3d_set_raw_input(on && !D.wt_absolute);
+    d3d_apply_cursor();
+    d3d_update_kbd_grab();
+    d3d_set_title();
+    d3d_notify(EMBED_N_GRAB, on);
+}
+
+/* client coordinates -> guest pixels */
+static void d3d_to_guest(int cx, int cy, int *gx, int *gy)
+{
+    int vw = D.view.right - D.view.left, vh = D.view.bottom - D.view.top;
+    int tw = D.scanout_on ? D.fw : D.sw, th = D.scanout_on ? D.fh : D.sh;
+
+    if (vw <= 0 || vh <= 0 || tw <= 0 || th <= 0) {
+        *gx = *gy = 0;
+        return;
+    }
+    *gx = (int)(((int64_t)(cx - D.view.left) * tw + tw / 2) / vw);
+    *gy = (int)(((int64_t)(cy - D.view.top) * th + th / 2) / vh);
+    if (D.view_scale >= 1 && vw == tw * (int)D.view_scale) {
+        /* integer scale: exact pixel boundaries */
+        *gx = (int)floor((double)(cx - D.view.left) / D.view_scale);
+        *gy = (int)floor((double)(cy - D.view.top) / D.view_scale);
+    }
+    *gx = MAX(0, MIN(*gx, tw - 1));
+    *gy = MAX(0, MIN(*gy, th - 1));
+}
+
+static void d3d_send_mouse(int cx, int cy)
+{
+    D3DEvent e = { .type = DE_MOUSE, .buttons = D.buttons };
+
+    d3d_to_guest(cx, cy, &e.x, &e.y);
+    e.w = D.scanout_on ? D.fw : D.sw;
+    e.h = D.scanout_on ? D.fh : D.sh;
+    d3d_post(&e);
+}
+
+static void d3d_button(uint32_t bit, bool down, int cx, int cy)
+{
+    uint32_t old = D.buttons;
+
+    if (down && D.parent && GetFocus() != D.hwnd) {
+        /* a child window does not get the keyboard focus by itself */
+        SetFocus(D.hwnd);
+    }
+    if (down) {
+        D.buttons |= bit;
+        SetCapture(D.hwnd);
+    } else {
+        D.buttons &= ~bit;
+        if (!D.buttons) {
+            ReleaseCapture();
+        }
+    }
+    if (!D.wt_absolute && !D.grab) {
+        /* relative pointer: the first click grabs it */
+        if (down && bit == 1) {
+            d3d_set_grab(true);
+            D.buttons = old;
+        }
+        return;
+    }
+    if (D.wt_absolute) {
+        d3d_send_mouse(cx, cy);
+    } else {
+        D3DEvent e = { .type = DE_BUTTONS, .buttons = D.buttons };
+        d3d_post(&e);
+    }
+}
+
+static void d3d_wheel(int delta, bool horizontal)
+{
+    int *acc = horizontal ? &D.hwheel_acc : &D.wheel_acc;
+
+    *acc += delta;
+    while (*acc >= WHEEL_DELTA || *acc <= -WHEEL_DELTA) {
+        D3DEvent e = { .type = DE_WHEEL };
+        if (*acc > 0) {
+            e.code = horizontal ? INPUT_BUTTON_WHEEL_RIGHT : INPUT_BUTTON_WHEEL_UP;
+            *acc -= WHEEL_DELTA;
+        } else {
+            e.code = horizontal ? INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_DOWN;
+            *acc += WHEEL_DELTA;
+        }
+        d3d_post(&e);
+    }
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* window thread: keyboard */
+
+/* Windows key message -> linux key code (keycodemapdb: AT set 1 scan codes) */
+static unsigned int d3d_key_code(WPARAM vk, LPARAM lp)
+{
+    unsigned int sc = (lp >> 16) & 0xff, code;
+    bool ext = lp & (1 << 24);
+
+    switch (vk) {
+    case VK_PAUSE:
+        return KEY_PAUSE;           /* E1 1D 45: no set 1 code of its own */
+    case VK_NUMLOCK:
+        return KEY_NUMLOCK;         /* Windows marks it extended, set 1 does not */
+    case VK_SNAPSHOT:
+        return KEY_SYSRQ;
+    }
+    if (!sc) {
+        /* synthesized input may lack the scan code */
+        code = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC_EX);
+        sc = code & 0xff;
+        ext = ext || (code & 0xff00) == 0xe000;
+    }
+    code = ext ? 0xe000 | sc : sc;
+    if (code >= qemu_input_map_atset1_to_linux_len) {
+        return 0;
+    }
+    return qemu_input_map_atset1_to_linux[code];
+}
+
+static bool d3d_hotkey(unsigned int lnx)
+{
+    if (D.parent) {
+        /* embedded: the launcher owns full screen, placement and its own hotkeys */
+        switch (lnx) {
+        case KEY_F:
+            d3d_notify(EMBED_N_FULLSCREEN, 0);
+            return true;
+        case KEY_G:
+            d3d_set_grab(!D.grab);
+            return true;
+        case KEY_0:
+            return true;        /* re-fit: the parent sizes the window */
+        case KEY_UP:
+            d3d_notify(EMBED_N_HOTKEY, VK_UP);
+            return true;
+        case KEY_DOWN:
+            d3d_notify(EMBED_N_HOTKEY, VK_DOWN);
+            return true;
+        case KEY_B:
+            d3d_notify(EMBED_N_HOTKEY, 'B');
+            return true;
+        case KEY_HOME:
+            d3d_notify(EMBED_N_HOTKEY, VK_HOME);
+            return true;
+        }
+        return false;
+    }
+    switch (lnx) {
+    case KEY_F:
+        d3d_set_fullscreen(!D.fullscreen);
+        d3d_update_kbd_grab();
+        return true;
+    case KEY_G:
+        d3d_set_grab(!D.grab);
+        return true;
+    case KEY_0:
+        if (D.fullscreen) {
+            d3d_set_fullscreen(false);
+        } else {
+            ShowWindow(D.hwnd, SW_RESTORE);
+            d3d_window_fit();
+        }
+        return true;
+    }
+    return false;
+}
+
+static void d3d_key(UINT msg, WPARAM vk, LPARAM lp)
+{
+    bool down = msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN;
+    unsigned int lnx;
+    D3DEvent e = { .type = DE_KEY };
+
+    if (vk == VK_CONTROL && !(lp & (1 << 24)) && down) {
+        /* AltGr sends a fake left Ctrl right before right Alt: drop it */
+        MSG next;
+        if (PeekMessageW(&next, D.hwnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE) &&
+            (next.message == WM_KEYDOWN || next.message == WM_SYSKEYDOWN) &&
+            next.wParam == VK_MENU && (next.lParam & (1 << 24)) &&
+            next.time == (DWORD)GetMessageTime()) {
+            return;
+        }
+    }
+    lnx = d3d_key_code(vk, lp);
+    if (!lnx) {
+        return;
+    }
+    if (lnx == KEY_LEFTCTRL) {
+        D.lctrl = down;
+    } else if (lnx == KEY_LEFTALT) {
+        D.lalt = down;
+    } else if (D.embed && lnx < KEY_CNT && test_bit(lnx, D.hk_held)) {
+        /*
+         * embedded: the auto-repeat and the release of a hotkey stay here as well (repeats
+         * that come through the low-level keyboard hook look like new presses)
+         */
+        if (!down) {
+            clear_bit(lnx, D.hk_held);
+        }
+        return;
+    } else if (down && D.lctrl && D.lalt && !(lp & (1 << 30)) && d3d_hotkey(lnx)) {
+        if (D.embed && lnx < KEY_CNT) {
+            set_bit(lnx, D.hk_held);
+        }
+        return;                 /* the hotkey itself does not reach the guest */
+    }
+    e.code = lnx;
+    e.down = down;
+    d3d_post(&e);
+}
+
+static void d3d_focus_lost(void)
+{
+    D3DEvent e = { .type = DE_RELEASE_ALL };
+
+    D.focus = false;
+    D.lctrl = D.lalt = false;
+    D.buttons = 0;
+    bitmap_zero(D.hk_held, KEY_CNT);
+    d3d_set_grab(false);
+    d3d_update_kbd_grab();
+    d3d_post(&e);
+}
+
+/*
+ * Embedded, on getting the focus: Ctrl+Alt (and a hotkey key) may already be held, e.g.
+ * when the launcher switched VMs on Ctrl+Alt+Down and focused this one. Hotkeys then work at
+ * once, and the held key's auto-repeat does not reach the guest. (The guest never saw these
+ * presses, so it gets no releases for them either: qkbd drops unmatched key-ups.)
+ */
+static void d3d_embed_focus_keys(void)
+{
+    static const struct { int vk; unsigned int lnx; } hk[] = {
+        { VK_UP, KEY_UP }, { VK_DOWN, KEY_DOWN }, { 'B', KEY_B }, { VK_HOME, KEY_HOME },
+        { 'F', KEY_F }, { 'G', KEY_G }, { '0', KEY_0 },
+    };
+    int i;
+
+    D.lctrl = GetAsyncKeyState(VK_LCONTROL) < 0;
+    D.lalt = GetAsyncKeyState(VK_LMENU) < 0;
+    bitmap_zero(D.hk_held, KEY_CNT);
+    if (D.lctrl && D.lalt) {
+        for (i = 0; i < ARRAY_SIZE(hk); i++) {
+            if (GetAsyncKeyState(hk[i].vk) < 0) {
+                set_bit(hk[i].lnx, D.hk_held);
+            }
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* window thread: embedding (parent=) */
+
+static bool d3d_create_window(HWND parent)
+{
+    if (parent) {
+        /* hidden: the parent positions, sizes and shows it */
+        D.hwnd = CreateWindowExW(0, D3D_CLASS_NAME, L"QEMU", WS_CHILD | WS_CLIPSIBLINGS,
+                                 0, 0, 640, 480, parent, NULL, GetModuleHandleW(NULL), NULL);
+    } else {
+        D.hwnd = CreateWindowExW(0, D3D_CLASS_NAME, L"QEMU", WS_OVERLAPPEDWINDOW,
+                                 CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL,
+                                 GetModuleHandleW(NULL), NULL);
+    }
+    return D.hwnd != NULL;
+}
+
+/* the parent's top-level window minimised/restored, or a window of its shown/hidden */
+static void CALLBACK d3d_win_event(HWINEVENTHOOK hook, DWORD event, HWND hwnd, LONG obj,
+                                   LONG child, DWORD thread, DWORD time)
+{
+    if (obj == OBJID_WINDOW && child == CHILDID_SELF && D.parent) {
+        d3d_update_paused();
+        /* at EVENT_SYSTEM_MINIMIZESTART the window may not be iconic yet: look again once
+           the change has settled (timer 2, one shot) */
+        if (D.hwnd) {
+            SetTimer(D.hwnd, 2, 250, NULL);
+        }
+    }
+}
+
+/*
+ * A child window gets no message when its parent (or the launcher's top-level window) is
+ * hidden or minimised: watch the parent's process for that (out of context: the events come
+ * through this thread's message loop).
+ */
+static void d3d_embed_watch(HWND parent)
+{
+    DWORD pid = 0;
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(D.ev_hook); i++) {
+        if (D.ev_hook[i]) {
+            UnhookWinEvent(D.ev_hook[i]);
+            D.ev_hook[i] = NULL;
+        }
+    }
+    if (parent && GetWindowThreadProcessId(parent, &pid) && pid) {
+        D.ev_hook[0] = SetWinEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND,
+                                       NULL, d3d_win_event, pid, 0, WINEVENT_OUTOFCONTEXT);
+        D.ev_hook[1] = SetWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE, NULL,
+                                       d3d_win_event, pid, 0, WINEVENT_OUTOFCONTEXT);
+    }
+}
+
+static void d3d_show_toplevel(void)
+{
+    /* QEMU_D3D11_NOACTIVATE=1: appear without taking the focus (tests) */
+    if (getenv("QEMU_D3D11_NOACTIVATE")) {
+        ShowWindow(D.hwnd, SW_SHOWNOACTIVATE);
+    } else {
+        ShowWindow(D.hwnd, SW_SHOWNORMAL);
+        SetForegroundWindow(D.hwnd);
+    }
+}
+
+/*
+ * DETACH (notify) or the parent vanished while our window survived (!notify): become a
+ * top-level window of our own where the child was, fitted to the picture.
+ */
+static void d3d_embed_detach(bool notify)
+{
+    HWND old = D.parent;
+    RECT r;
+
+    if (!old) {
+        return;
+    }
+    d3d_set_grab(false);                    /* GRAB 0 to the parent if it was grabbed */
+    if (notify && D.focus) {
+        d3d_notify(EMBED_N_FOCUS, 0);       /* the parent no longer hosts the focus */
+    }
+    D.parent = NULL;                        /* nothing more goes to the old parent */
+    D.parent_closed = false;
+    d3d_embed_watch(NULL);
+    GetWindowRect(D.hwnd, &r);
+    ShowWindow(D.hwnd, SW_HIDE);
+    SetParent(D.hwnd, NULL);
+    SetWindowLongPtrW(D.hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS);
+    SetWindowPos(D.hwnd, NULL, r.left, r.top, MAX(r.right - r.left, 64),
+                 MAX(r.bottom - r.top, 64), SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE);
+    /* keep the child's centre if that is on a monitor, else centre on the monitor */
+    D.window_shown = r.right > r.left && MonitorFromRect(&r, MONITOR_DEFAULTTONULL);
+    d3d_window_fit();
+    D.window_shown = true;
+    d3d_show_toplevel();
+    d3d_set_title();
+    d3d_update_paused();
+    D.size_w = D.size_h = 0;
+    if (notify) {
+        PostMessageW(old, D.msg_notify, EMBED_N_DETACHED, (LPARAM)D.hwnd);
+    }
+    info_report("d3d11: %s: now a window of its own", notify ? "detached" :
+                "the parent window is gone");
+}
+
+/* ATTACH: become a hidden child window of @parent (it positions, sizes and shows us) */
+static void d3d_embed_attach(HWND parent)
+{
+    if (!parent || !IsWindow(parent) || parent == D.hwnd || IsChild(D.hwnd, parent)) {
+        warn_report("d3d11: ATTACH to an invalid window 0x%llx ignored",
+                    (unsigned long long)(uintptr_t)parent);
+        return;
+    }
+    if (D.fullscreen) {
+        d3d_set_fullscreen(false);
+    }
+    d3d_set_grab(false);
+    if (D.parent && D.parent != parent && D.focus) {
+        d3d_notify(EMBED_N_FOCUS, 0);
+    }
+    D.parent = NULL;                        /* (no notifications while re-parenting) */
+    ShowWindow(D.hwnd, SW_HIDE);
+    SetWindowLongPtrW(D.hwnd, GWL_STYLE, WS_CHILD | WS_CLIPSIBLINGS);
+    SetParent(D.hwnd, parent);
+    SetWindowPos(D.hwnd, NULL, 0, 0, D.sw > 0 ? D.sw : 640, D.sh > 0 ? D.sh : 480,
+                 SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE);
+    D.parent = parent;
+    D.parent_closed = false;
+    D.minimized = false;
+    D.window_shown = true;
+    d3d_embed_watch(parent);
+    d3d_update_paused();
+    d3d_notify(EMBED_N_READY, (LPARAM)D.hwnd);
+    d3d_notify_size(true);
+    info_report("d3d11: attached to window 0x%llx", (unsigned long long)(uintptr_t)parent);
+}
+
+static void d3d_embed_command(WPARAM cmd, LPARAM lp)
+{
+    switch (cmd) {
+    case EMBED_CMD_DETACH:
+        d3d_embed_detach(true);
+        break;
+    case EMBED_CMD_ATTACH:
+        d3d_embed_attach((HWND)lp);
+        break;
+    case EMBED_CMD_RELEASE_GRAB:
+        if (D.grab) {
+            d3d_set_grab(false);
+        }
+        break;
+    case EMBED_CMD_FOCUS:
+        SetFocus(D.hwnd);
+        break;
+    }
+}
+
+/*
+ * Embedded: Windows destroys a child window with its parent, also across processes, when the
+ * launcher exits or crashes. Drop everything tied to the window; the thread's loop then makes
+ * a new top-level window (d3d_window_recreate).
+ */
+static void d3d_window_gone(HWND hwnd)
+{
+    D3DEvent e = { .type = DE_RELEASE_ALL };
+
+    if (!GetWindowRect(hwnd, &D.lost_rect)) {
+        D.lost_rect = D.last_rect;          /* already gone: where it was last seen */
+    }
+    D.window_lost = true;
+    D.parent = NULL;
+    D.parent_closed = false;
+    d3d_embed_watch(NULL);
+    if (D.grab) {
+        D.grab = false;
+        ClipCursor(NULL);
+    }
+    if (D.raw_input) {
+        RAWINPUTDEVICE rid = { .usUsagePage = 0x01, .usUsage = 0x02,
+                               .dwFlags = RIDEV_REMOVE, .hwndTarget = NULL };
+        RegisterRawInputDevices(&rid, 1, sizeof(rid));
+        D.raw_input = false;
+    }
+    if (D.kbd_grab) {
+        D.kbd_grab = false;
+        win32_kbd_set_grab(false);
+    }
+    win32_kbd_set_window(NULL);
+    D.focus = false;
+    D.mouse_inside = false;
+    D.lctrl = D.lalt = false;
+    D.buttons = 0;
+    bitmap_zero(D.hk_held, KEY_CNT);
+    D.size_w = D.size_h = 0;
+    d3d_post(&e);
+}
+
+static void d3d_window_recreate(void)
+{
+    RECT r = D.lost_rect;
+    D3DEvent e = { .type = DE_WINDOW_ID };
+    bool placed = false;
+
+    D.window_lost = false;
+    d3d_release_swapchain();
+    D.hwnd = NULL;
+    D.fullscreen = false;
+    D.minimized = false;
+    if (!d3d_create_window(NULL) || !d3d_create_swapchain()) {
+        error_report("d3d11: the parent window is gone and a new window could not be made: "
+                     "the VM keeps running without a display");
+        if (D.hwnd) {
+            D.quitting = true;
+            DestroyWindow(D.hwnd);
+            D.hwnd = NULL;
+        }
+        return;
+    }
+    D.dpi = d3d_dpi();
+    d3d_resize_buffers();
+    e.x = (int)(intptr_t)D.hwnd;
+    d3d_post(&e);
+    /* where the child was, if that is on a monitor, else centred on the monitor */
+    if (r.right > r.left && r.bottom > r.top && MonitorFromRect(&r, MONITOR_DEFAULTTONULL)) {
+        SetWindowPos(D.hwnd, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top,
+                     SWP_NOZORDER | SWP_NOACTIVATE);
+        placed = true;
+    }
+    d3d_set_title();
+    if (D.sw > 0 && D.sh > 0) {
+        D.window_shown = placed;            /* d3d_window_fit: keep the centre or centre */
+        d3d_window_fit();
+        D.window_shown = true;
+        d3d_show_toplevel();
+    } else {
+        D.window_shown = false;             /* the first surface fits and shows it */
+    }
+    if (D.cursor) {
+        d3d_rebuild_cursor();
+    }
+    D.need_render = true;
+    d3d_update_paused();
+    info_report("d3d11: the parent window is gone: continuing in a window of its own");
+}
+
+/* ------------------------------------------------------------------------------------------ */
+/* window thread: window procedure and loop */
+
+static void d3d_track_leave(void)
+{
+    TRACKMOUSEEVENT t = { .cbSize = sizeof(t), .dwFlags = TME_LEAVE, .hwndTrack = D.hwnd };
+    TrackMouseEvent(&t);
+}
+
+static void d3d_work(void);
+
+static LRESULT CALLBACK d3d_wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
+{
+    if (D.embed && msg == D.msg_cmd && msg) {
+        if (hwnd == D.hwnd) {
+            d3d_embed_command(wp, lp);
+        }
+        return 0;
+    }
+    switch (msg) {
+    case WM_CLOSE: {
+        D3DEvent e = { .type = DE_CLOSE };
+        d3d_post(&e);
+        return 0;
+    }
+    case WM_ERASEBKGND:
+        return 1;
+    case WM_ENTERSIZEMOVE:
+        SetTimer(hwnd, 1, 2, NULL);
+        return 0;
+    case WM_EXITSIZEMOVE:
+        KillTimer(hwnd, 1);
+        return 0;
+    case WM_TIMER:
+        if (wp == 1) {
+            d3d_work();
+            return 0;
+        }
+        if (wp == 2) {
+            KillTimer(hwnd, 2);
+            d3d_update_paused();
+            return 0;
+        }
+        break;
+    case WM_D3D_SNAP:
+        D.snap_req = true;
+        D.need_render = true;
+        return 0;
+    case WM_PAINT:
+        ValidateRect(hwnd, NULL);
+        D.need_render = true;
+        return 0;
+    case WM_SIZE:
+        if (D.embed && hwnd != D.hwnd) {
+            break;                          /* a window being made or one that is gone */
+        }
+        if (wp == SIZE_MINIMIZED) {
+            D.minimized = true;
+            d3d_update_paused();
+            return 0;
+        }
+        D.minimized = false;
+        d3d_update_paused();
+        if (D.swap) {
+            d3d_resize_buffers();
+            if (D.grab) {
+                RECT rc;
+                GetClientRect(D.hwnd, &rc);
+                MapWindowPoints(D.hwnd, NULL, (POINT *)&rc, 2);
+                ClipCursor(&rc);
+            }
+            /* the picture scale (and with it the cursor's) may change */
+            {
+                bool linear;
+                d3d_compute_view(D.scanout_on ? D.fw : D.sw, D.scanout_on ? D.fh : D.sh,
+                                 &linear);
+                if (D.cursor && d3d_cursor_scale() != D.cursor_scale) {
+                    d3d_rebuild_cursor();
+                }
+            }
+            D.need_render = true;
+        }
+        return 0;
+    case WM_MOVE:
+        if (D.parent && D.grab && hwnd == D.hwnd) {
+            /* embedded: the parent moved us; keep the pointer confined to where we are */
+            RECT rc;
+            GetClientRect(D.hwnd, &rc);
+            MapWindowPoints(D.hwnd, NULL, (POINT *)&rc, 2);
+            ClipCursor(&rc);
+        }
+        break;
+    case WM_SHOWWINDOW:
+        if (D.parent && (lp == SW_PARENTCLOSING || lp == SW_PARENTOPENING)) {
+            D.parent_closed = !wp;
+            d3d_update_paused();
+        }
+        break;
+    case WM_WINDOWPOSCHANGED:
+        if (D.embed && hwnd == D.hwnd) {
+            GetWindowRect(hwnd, &D.last_rect);
+        }
+        if (D.parent && hwnd == D.hwnd &&
+            (((WINDOWPOS *)lp)->flags & (SWP_SHOWWINDOW | SWP_HIDEWINDOW))) {
+            d3d_update_paused();            /* the parent showed or hid us */
+        }
+        break;                              /* (DefWindowProc sends WM_SIZE / WM_MOVE) */
+    case WM_DESTROY:
+        if (D.embed && !D.quitting && hwnd == D.hwnd) {
+            d3d_window_gone(hwnd);          /* the parent died: d3d_window_recreate */
+        }
+        break;
+    case WM_NCDESTROY:
+        if (hwnd == D.hwnd && D.window_lost) {
+            D.hwnd = NULL;
+        }
+        break;
+    case WM_DPICHANGED: {
+        /* keep the client area (the picture is in physical pixels) */
+        RECT *sug = (RECT *)lp, b;
+        RECT cr;
+        GetClientRect(hwnd, &cr);
+        D.dpi = HIWORD(wp);
+        if (D.fullscreen) {
+            return 0;
+        }
+        {
+            RECT r = { 0, 0, cr.right, cr.bottom };
+            DWORD style = GetWindowLongW(hwnd, GWL_STYLE), ex = GetWindowLongW(hwnd, GWL_EXSTYLE);
+            if (p_AdjustWindowRectExForDpi) {
+                p_AdjustWindowRectExForDpi(&r, style, FALSE, ex, D.dpi);
+            }
+            b = r;
+        }
+        SetWindowPos(hwnd, NULL, sug->left, sug->top, b.right - b.left, b.bottom - b.top,
+                     SWP_NOZORDER | SWP_NOACTIVATE);
+        return 0;
+    }
+    case WM_GETDPISCALEDSIZE: {
+        /* client size stays the same in physical pixels */
+        RECT cr, r;
+        SIZE *sz = (SIZE *)lp;
+        DWORD style = GetWindowLongW(hwnd, GWL_STYLE), ex = GetWindowLongW(hwnd, GWL_EXSTYLE);
+        GetClientRect(hwnd, &cr);
+        r = cr;
+        if (p_AdjustWindowRectExForDpi) {
+            p_AdjustWindowRectExForDpi(&r, style, FALSE, ex, (UINT)wp);
+            sz->cx = r.right - r.left;
+            sz->cy = r.bottom - r.top;
+            return TRUE;
+        }
+        return FALSE;
+    }
+    case WM_SETCURSOR:
+        if (LOWORD(lp) == HTCLIENT) {
+            SetCursor(d3d_client_cursor());
+            return TRUE;
+        }
+        break;
+    case WM_SETFOCUS:
+        if (D.parent) {
+            d3d_embed_focus_keys();
+        }
+        D.focus = true;
+        win32_kbd_set_window(hwnd);
+        d3d_update_kbd_grab();
+        d3d_notify(EMBED_N_FOCUS, 1);
+        return 0;
+    case WM_KILLFOCUS:
+        d3d_focus_lost();
+        d3d_notify(EMBED_N_FOCUS, 0);
+        return 0;
+    case WM_ACTIVATE:
+        if (LOWORD(wp) == WA_INACTIVE && D.grab) {
+            d3d_set_grab(false);
+        }
+        break;
+    case WM_KEYDOWN:
+    case WM_KEYUP:
+    case WM_SYSKEYDOWN:
+    case WM_SYSKEYUP:
+        d3d_key(msg, wp, lp);
+        return 0;
+    case WM_CHAR:
+    case WM_SYSCHAR:
+    case WM_DEADCHAR:
+    case WM_SYSDEADCHAR:
+        return 0;
+    case WM_SYSCOMMAND:
+        if ((wp & 0xfff0) == SC_KEYMENU) {
+            return 0;           /* Alt / F10 must not open the system menu */
+        }
+        break;
+    case WM_MOUSEMOVE:
+        if (!D.mouse_inside) {
+            D.mouse_inside = true;
+            d3d_track_leave();
+            d3d_update_kbd_grab();
+        }
+        if (D.wt_absolute) {
+            d3d_send_mouse(GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
+        }
+        return 0;
+    case WM_MOUSELEAVE:
+        D.mouse_inside = false;
+        d3d_update_kbd_grab();
+        return 0;
+    case WM_LBUTTONDOWN:
+    case WM_LBUTTONUP:
+        d3d_button(1 << 0, msg == WM_LBUTTONDOWN, GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
+        return 0;
+    case WM_MBUTTONDOWN:
+    case WM_MBUTTONUP:
+        d3d_button(1 << 1, msg == WM_MBUTTONDOWN, GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
+        return 0;
+    case WM_RBUTTONDOWN:
+    case WM_RBUTTONUP:
+        d3d_button(1 << 2, msg == WM_RBUTTONDOWN, GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
+        return 0;
+    case WM_XBUTTONDOWN:
+    case WM_XBUTTONUP:
+        d3d_button(GET_XBUTTON_WPARAM(wp) == XBUTTON1 ? 1 << 3 : 1 << 4,
+                   msg == WM_XBUTTONDOWN, GET_X_LPARAM(lp), GET_Y_LPARAM(lp));
+        return TRUE;
+    case WM_LBUTTONDBLCLK:
+        break;
+    case WM_MOUSEWHEEL:
+        d3d_wheel(GET_WHEEL_DELTA_WPARAM(wp), false);
+        return 0;
+    case WM_MOUSEHWHEEL:
+        d3d_wheel(GET_WHEEL_DELTA_WPARAM(wp), true);
+        return 0;
+    case WM_INPUT: {
+        RAWINPUT ri;
+        UINT size = sizeof(ri);
+        if (D.grab && !D.wt_absolute &&
+            GetRawInputData((HRAWINPUT)lp, RID_INPUT, &ri, &size, sizeof(RAWINPUTHEADER)) !=
+            (UINT)-1 && ri.header.dwType == RIM_TYPEMOUSE &&
+            !(ri.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) &&
+            (ri.data.mouse.lLastX || ri.data.mouse.lLastY)) {
+            D3DEvent e = { .type = DE_REL, .x = ri.data.mouse.lLastX,
+                           .y = ri.data.mouse.lLastY };
+            d3d_post(&e);
+        }
+        break;
+    }
+    case WM_CAPTURECHANGED:
+        if ((HWND)lp != hwnd && D.buttons) {
+            D3DEvent e = { .type = DE_BUTTONS, .buttons = 0 };
+            D.buttons = 0;
+            d3d_post(&e);
+        }
+        break;
+    }
+    return DefWindowProcW(hwnd, msg, wp, lp);
+}
+
+/* apply what the main loop sent */
+static void d3d_process_pending(void)
+{
+    uint32_t pend;
+    QEMUCursor *cnew = NULL;
+    bool cursor_on, absolute;
+    int sw = 0, sh = 0;
+
+    qemu_mutex_lock(&D.lock);
+    pend = D.pending & ~PEND_DIRTY;
+    D.pending &= PEND_DIRTY;
+    if (pend & PEND_SURFACE && D.surface) {
+        sw = surface_width(D.surface);
+        sh = surface_height(D.surface);
+    }
+    if (pend & PEND_CURSOR) {
+        cnew = D.cursor_new;
+        D.cursor_new = NULL;
+    }
+    cursor_on = D.guest_cursor_on;
+    absolute = D.absolute;
+    qemu_mutex_unlock(&D.lock);
+
+    if (pend & PEND_SURFACE && sw > 0 && sh > 0) {
+        if (sw != D.sw || sh != D.sh || !D.surf_tex) {
+            D.sw = sw;
+            D.sh = sh;
+            if (!d3d_make_texture(sw, sh, DXGI_FORMAT_B8G8R8X8_UNORM, &D.surf_tex,
+                                  &D.surf_srv)) {
+                error_report("d3d11: surface texture %dx%d failed", sw, sh);
+            }
+            d3d_window_fit();
+            if (!D.window_shown) {
+                D.window_shown = true;
+                /* QEMU_D3D11_NOACTIVATE=1: appear without taking the focus (tests) */
+                ShowWindow(D.hwnd, getenv("QEMU_D3D11_NOACTIVATE") ? SW_SHOWNOACTIVATE
+                                                                   : SW_SHOWNORMAL);
+                if (D.opts->has_full_screen && D.opts->full_screen && !D.embed) {
+                    d3d_set_fullscreen(true);
+                }
+            }
+            d3d_notify_size(false);
+        }
+        /* ring frames of the old mode no longer apply */
+        D.scanout_on = false;
+        d3d_upload_surface(true);
+        D.need_render = true;
+    }
+    if (pend & PEND_DISABLE) {
+        D.scanout_on = false;
+        d3d_upload_surface(true);
+        D.need_render = true;
+    }
+    if (pend & PEND_CURSOR && cnew) {
+        if (D.cursor) {
+            cursor_unref(D.cursor);
+        }
+        D.cursor = cnew;
+        d3d_rebuild_cursor();
+    }
+    if (pend & (PEND_MOUSE | PEND_MOUSEMODE)) {
+        bool mode_changed = absolute != D.wt_absolute;
+        D.wt_cursor_on = cursor_on;
+        D.wt_absolute = absolute;
+        if (mode_changed && D.grab) {
+            d3d_set_raw_input(!absolute);
+        }
+        d3d_apply_cursor();
+        d3d_update_kbd_grab();
+    }
+    if (pend & PEND_TITLE) {
+        d3d_set_title();
+    }
+    if (pend & PEND_QUIT) {
+        D.quitting = true;
+        PostQuitMessage(0);
+    }
+}
+
+/* one round of the window thread's work (also from WM_TIMER while Windows runs a modal
+   move/size loop, which would otherwise freeze the picture during a window drag) */
+static void d3d_work(void)
+{
+    int64_t t;
+
+    d3d_process_pending();
+    d3d_upload_surface(false);
+    t = d3d_take_frame();
+    if (t) {
+        D.t_frame = t;
+    }
+    d3d_release_buffers();
+    if (D.need_render && !D.paused && !D.slot_free && D.waitable &&
+        WaitForSingleObject(D.waitable, 0) == WAIT_OBJECT_0) {
+        D.slot_free = true;
+    }
+    if (D.need_render && !D.paused && (D.slot_free || !D.waitable)) {
+        D.need_render = false;
+        D.slot_free = false;
+        d3d_render(D.t_frame);
+        D.t_frame = 0;
+    }
+    if (D.stats) {
+        d3d_stats_frame_statistics();
+        d3d_stats_report();
+    }
+}
+
+static void *d3d_thread(void *opaque)
+{
+    WNDCLASSEXW wc = {
+        .cbSize = sizeof(wc), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = d3d_wndproc,
+        .hInstance = GetModuleHandleW(NULL), .lpszClassName = D3D_CLASS_NAME,
+        .hCursor = NULL, .hbrBackground = NULL,
+    };
+    HMODULE user32 = GetModuleHandleW(L"user32.dll");
+    bool quit = false;
+
+    p_GetDpiForWindow = (void *)GetProcAddress(user32, "GetDpiForWindow");
+    p_AdjustWindowRectExForDpi = (void *)GetProcAddress(user32, "AdjustWindowRectExForDpi");
+    p_SetThreadDpiAwarenessContext = (void *)GetProcAddress(user32,
+                                                            "SetThreadDpiAwarenessContext");
+    if (p_SetThreadDpiAwarenessContext) {
+        /* guest pixels map 1:1 to physical pixels whatever the display scaling */
+        p_SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+    }
+    /* presentation is latency-critical, like qegl's render thread */
+    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
+
+    wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(1));
+    RegisterClassExW(&wc);
+    if (D.embed) {
+        D.msg_cmd = RegisterWindowMessageW(D3D_EMBED_CMD_NAME);
+        D.msg_notify = RegisterWindowMessageW(D3D_EMBED_NOTIFY_NAME);
+        if (!IsWindow(D.parent)) {
+            warn_report("d3d11: parent window 0x%llx does not exist: using a window of its "
+                        "own", (unsigned long long)(uintptr_t)D.parent);
+            D.parent = NULL;
+        } else if (!d3d_create_window(D.parent)) {
+            warn_report("d3d11: no child window of 0x%llx (error %lu): using a window of its "
+                        "own", (unsigned long long)(uintptr_t)D.parent, GetLastError());
+            D.parent = NULL;
+        }
+    }
+    if (!D.hwnd) {
+        d3d_create_window(NULL);
+    }
+    if (!D.hwnd || !d3d_create_device() || !d3d_create_swapchain()) {
+        D.init_ok = false;
+        SetEvent(D.ready);
+        return NULL;
+    }
+    D.dpi = d3d_dpi();
+    d3d_resize_buffers();
+    D.init_ok = true;
+    if (D.parent) {
+        /* created hidden: the parent shows it (QEMU never shows itself while embedded) */
+        D.window_shown = true;
+        d3d_embed_watch(D.parent);
+        d3d_update_paused();
+        d3d_notify(EMBED_N_READY, (LPARAM)D.hwnd);
+    }
+    SetEvent(D.ready);
+
+    while (!quit) {
+        HANDLE h[3];
+        DWORD n = 0, r;
+        MSG m;
+
+        h[n++] = D.wake;
+        h[n++] = D.rel_event;
+        if (D.need_render && !D.slot_free && D.waitable) {
+            h[n++] = D.waitable;
+        }
+        r = MsgWaitForMultipleObjectsEx(n, h, D.stats ? 500 : INFINITE, QS_ALLINPUT,
+                                        MWMO_INPUTAVAILABLE);
+        if (r == WAIT_OBJECT_0 + 2) {
+            D.slot_free = true;
+        }
+        while (PeekMessageW(&m, NULL, 0, 0, PM_REMOVE)) {
+            if (m.message == WM_QUIT) {
+                quit = true;
+                break;
+            }
+            DispatchMessageW(&m);
+        }
+        if (D.embed && !quit) {
+            if (!D.window_lost && D.hwnd && !IsWindow(D.hwnd)) {
+                /*
+                 * Destroyed without a WM_DESTROY reaching us: that is what happens when
+                 * the parent's process is killed (its thread's windows go, ours with them).
+                 */
+                d3d_window_gone(D.hwnd);
+                D.hwnd = NULL;
+            }
+            if (D.window_lost) {
+                d3d_window_recreate();      /* the parent died and took our window along */
+            } else if (D.parent && !IsWindow(D.parent) && D.hwnd) {
+                d3d_embed_detach(false);    /* the parent is gone but our window is not */
+            }
+        }
+        d3d_work();
+    }
+    return NULL;
+}
+
+/* ------------------------------------------------------------------------------------------ */
+
+static const DisplayChangeListenerOps d3d_dcl_ops = {
+    .dpy_name             = "d3d11",
+    .dpy_gfx_update       = d3d_gfx_update,
+    .dpy_gfx_switch       = d3d_gfx_switch,
+    .dpy_gfx_check_format = d3d_check_format,
+    .dpy_refresh          = d3d_refresh,
+    .dpy_mouse_set        = d3d_mouse_set,
+    .dpy_cursor_define    = d3d_cursor_define,
+    .dpy_d3d_scanout      = d3d_get_scanout,
+};
+
+static void d3d_display_init(DisplayState *ds, DisplayOptions *o)
+{
+    DisplayD3D11 *opt = &o->u.d3d11;
+    LARGE_INTEGER f;
+
+    assert(o->type == DISPLAY_TYPE_D3D11);
+    D.opts = o;
+    D.integer_scale = !opt->has_integer_scale || opt->integer_scale;
+    D.vsync = !opt->has_vsync || opt->vsync;
+    D.zero_copy = !opt->has_zero_copy || opt->zero_copy;
+    D.show_cursor = o->has_show_cursor && o->show_cursor;
+    D.window_close = !o->has_window_close || o->window_close;
+    D.embed = opt->has_parent;
+    D.parent = opt->has_parent ? (HWND)(uintptr_t)opt->parent : NULL;
+    D.stats = getenv("QEMU_D3D11_STATS") && atoi(getenv("QEMU_D3D11_STATS"));
+    QueryPerformanceFrequency(&f);
+    D.qpf = f.QuadPart;
+    D.st_t0 = qpc_now();
+
+    D.con = qemu_console_lookup_default();
+    if (!D.con) {
+        error_report("d3d11: no console");
+        exit(1);
+    }
+    qemu_mutex_init(&D.lock);
+    qemu_mutex_init(&D.in_lock);
+    qemu_mutex_init(&D.ring_lock);
+    D.events = g_array_new(false, false, sizeof(D3DEvent));
+    D.retired = g_ptr_array_new();
+    D.input_bh = qemu_bh_new(d3d_input_bh, NULL);
+    D.kbd = qkbd_state_init(D.con);
+    D.scanout.ops = &d3d_scanout_ops;
+    D.scanout.sink = &D;
+    D.absolute = qemu_input_is_absolute(D.con);
+    D.wake = CreateEventW(NULL, FALSE, FALSE, NULL);
+    D.ready = CreateEventW(NULL, TRUE, FALSE, NULL);
+    D.last_running = runstate_is_running();
+    d3d_update_title();
+
+    qemu_thread_create(&D.thread, "d3d11-window", d3d_thread, NULL, QEMU_THREAD_DETACHED);
+    WaitForSingleObject(D.ready, INFINITE);
+    if (!D.init_ok) {
+        error_report("d3d11: could not create the window / Direct3D 11 device");
+        exit(1);
+    }
+    qemu_console_set_window_id(D.con, (int)(intptr_t)D.hwnd);
+    qemu_console_register_listener(D.con, &D.dcl, &d3d_dcl_ops);
+    D.mouse_mode_notifier.notify = d3d_mouse_mode_change;
+    qemu_add_mouse_mode_change_notifier(&D.mouse_mode_notifier);
+    d3d_mouse_mode_change(NULL, NULL);
+    qemu_add_vm_change_state_handler(d3d_vm_state_change, NULL);
+}
+
+static void d3d_display_cleanup(void)
+{
+    if (D.hwnd) {
+        ClipCursor(NULL);
+        D.quitting = true;
+        d3d_wake(PEND_QUIT);
+    }
+}
+
+static QemuDisplay qemu_display_d3d11 = {
+    .type       = DISPLAY_TYPE_D3D11,
+    .init       = d3d_display_init,
+    .cleanup    = d3d_display_cleanup,
+};
+
+static void register_d3d11(void)
+{
+    qemu_display_register(&qemu_display_d3d11);
+}
+
+type_init(register_d3d11);
diff --git a/ui/meson.build b/ui/meson.build
index d345f6c..22ef5c9 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -147,10 +147,11 @@ if dbus_display
   ui_modules += {'dbus' : dbus_ss}
 endif
 
+if host_os == 'windows' and (gtk.found() or d3d11.found())
+  system_ss.add(files('win32-kbd-hook.c'))
+endif
+
 if gtk.found()
-  if host_os == 'windows'
-    system_ss.add(files('win32-kbd-hook.c'))
-  endif
 
   gtk_ss = ss.source_set()
   gtk_ss.add(gtk, vte, pixman, files('gtk.c'))
@@ -173,6 +174,12 @@ if sdl.found()
   ui_modules += {'sdl' : sdl_ss}
 endif
 
+if d3d11.found()
+  d3d11_ss = ss.source_set()
+  d3d11_ss.add(d3d11, pixman, glib, files('d3d11.c'))
+  ui_modules += {'d3d11' : d3d11_ss}
+endif
+
 if spice.found()
   spice_core_ss = ss.source_set()
   spice_core_ss.add(spice, pixman, files(
diff --git a/ui/sdl2-2d.c b/ui/sdl2-2d.c
index 68a3aff..0e3edab 100644
--- a/ui/sdl2-2d.c
+++ b/ui/sdl2-2d.c
@@ -121,6 +121,7 @@ void sdl2_2d_switch(DisplayChangeListener *dcl,
                                       SDL_TEXTUREACCESS_STREAMING,
                                       surface_width(new_surface),
                                       surface_height(new_surface));
+    sdl2_update_scaling(scon);
     sdl2_2d_redraw(scon);
 }
 
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 1c97d23..1abd7f9 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -33,6 +33,7 @@
 #include "system/runstate-action.h"
 #include "system/system.h"
 #include "qemu/log.h"
+#include "qemu/error-report.h"
 #include "qemu-main.h"
 
 #ifdef CONFIG_X11
@@ -56,7 +57,107 @@ static int absolute_enabled;
 static bool guest_cursor;
 static int guest_x, guest_y;
 static SDL_Cursor *guest_sprite;
+static QEMUCursor *guest_cursor_def;    /* the guest's cursor image (guest_sprite is scaled) */
+static int guest_sprite_scale;
 static Notifier mouse_mode_notifier;
+static void sdl_rescale_guest_sprite(struct sdl2_console *scon);
+
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+/*
+ * SDL2 copies colour cursors with CopyImage() so that they follow the Windows pointer-size
+ * setting: with a larger pointer size Windows draws them enlarged by a non-integer factor
+ * (size 2: 32x32 shown as 48x48), blurry and jagged. The guest cursor is shown through a
+ * CreateIconIndirect() cursor instead, which Windows draws pixel for pixel; it replaces SDL's
+ * copy whenever SDL would show the guest sprite.
+ */
+static HCURSOR guest_hcursor;
+static WNDPROC sdl_wndproc;
+
+static HCURSOR sdl_create_hcursor(SDL_Surface *s, int hot_x, int hot_y)
+{
+    BITMAPV5HEADER bi = {
+        .bV5Size = sizeof(bi), .bV5Width = s->w, .bV5Height = -s->h, .bV5Planes = 1,
+        .bV5BitCount = 32, .bV5Compression = BI_BITFIELDS, .bV5RedMask = 0x00ff0000,
+        .bV5GreenMask = 0x0000ff00, .bV5BlueMask = 0x000000ff, .bV5AlphaMask = 0xff000000,
+    };
+    ICONINFO ii = { .fIcon = FALSE, .xHotspot = hot_x, .yHotspot = hot_y };
+    int mask_stride = (s->w + 15) / 16 * 2;     /* monochrome rows are WORD aligned */
+    g_autofree uint8_t *mask = g_malloc0(mask_stride * s->h);
+    HCURSOR cursor = NULL;
+    void *bits;
+    int x, y;
+
+    /* AND mask: transparent where alpha is 0 (Windows uses the alpha channel otherwise) */
+    for (y = 0; y < s->h; y++) {
+        const uint32_t *row = (const uint32_t *)((const uint8_t *)s->pixels + y * s->pitch);
+        for (x = 0; x < s->w; x++) {
+            if (!(row[x] >> 24)) {
+                mask[y * mask_stride + x / 8] |= 0x80 >> (x % 8);
+            }
+        }
+    }
+    ii.hbmColor = CreateDIBSection(NULL, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, 0);
+    ii.hbmMask = CreateBitmap(s->w, s->h, 1, 1, mask);
+    if (ii.hbmColor && ii.hbmMask) {
+        for (y = 0; y < s->h; y++) {
+            memcpy((uint8_t *)bits + y * s->w * 4, (uint8_t *)s->pixels + y * s->pitch,
+                   s->w * 4);
+        }
+        cursor = (HCURSOR)CreateIconIndirect(&ii);
+    }
+    if (ii.hbmColor) {
+        DeleteObject(ii.hbmColor);
+    }
+    if (ii.hbmMask) {
+        DeleteObject(ii.hbmMask);
+    }
+    return cursor;
+}
+
+static bool sdl_guest_hcursor_active(void)
+{
+    return guest_hcursor && guest_sprite && SDL_GetCursor() == guest_sprite &&
+           SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE;
+}
+
+static LRESULT CALLBACK sdl_cursor_wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
+{
+    if (msg == WM_SETCURSOR && LOWORD(lp) == HTCLIENT && sdl_guest_hcursor_active()) {
+        SetCursor(guest_hcursor);
+        return TRUE;
+    }
+    return CallWindowProcW(sdl_wndproc, hwnd, msg, wp, lp);
+}
+
+/* every SDL window shares SDL's window procedure, so one saved pointer serves all */
+static void sdl_hook_window_cursor(SDL_Window *window)
+{
+    SDL_SysWMinfo info;
+    WNDPROC old;
+
+    SDL_VERSION(&info.version);
+    if (!SDL_GetWindowWMInfo(window, &info) || info.subsystem != SDL_SYSWM_WINDOWS) {
+        return;
+    }
+    old = (WNDPROC)GetWindowLongPtrW(info.info.win.window, GWLP_WNDPROC);
+    if (old == sdl_cursor_wndproc || (sdl_wndproc && old != sdl_wndproc)) {
+        return;
+    }
+    sdl_wndproc = old;
+    SetWindowLongPtrW(info.info.win.window, GWLP_WNDPROC, (LONG_PTR)sdl_cursor_wndproc);
+}
+#endif
+
+/* show the guest's cursor (SDL_SetCursor applies SDL's own copy immediately) */
+static void sdl_set_guest_cursor(void)
+{
+    SDL_SetCursor(guest_sprite);
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+    if (sdl_guest_hcursor_active()) {
+        SetCursor(guest_hcursor);
+    }
+#endif
+}
 
 #define SDL2_REFRESH_INTERVAL_BUSY 10
 #define SDL2_MAX_IDLE_COUNT (2 * GUI_REFRESH_INTERVAL_DEFAULT \
@@ -108,6 +209,9 @@ void sdl2_window_create(struct sdl2_console *scon)
                                          surface_width(scon->surface),
                                          surface_height(scon->surface),
                                          flags);
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+    sdl_hook_window_cursor(scon->real_window);
+#endif
     if (scon->opengl) {
         const char *driver = "opengl";
 
@@ -129,6 +233,9 @@ void sdl2_window_create(struct sdl2_console *scon)
     } else {
         /* The SDL renderer is only used by sdl2-2D, when OpenGL is disabled */
         scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0);
+        if (scon->opts->u.sdl.has_integer_scale && scon->opts->u.sdl.integer_scale) {
+            SDL_RenderSetIntegerScale(scon->real_renderer, SDL_TRUE);
+        }
     }
 
     sdl_update_caption(scon);
@@ -152,15 +259,70 @@ void sdl2_window_destroy(struct sdl2_console *scon)
     scon->real_window = NULL;
 }
 
+/*
+ * Integer scaling while the picture fits at 1x or more. A picture bigger than the window (a
+ * guest mode bigger than the screen) is scaled down smoothly to fit instead: SDL's integer
+ * scaling never goes below 1x and would crop it.
+ */
+void sdl2_update_scaling(struct sdl2_console *scon)
+{
+    int ow, oh;
+    bool fits;
+
+    if (scon->opengl || !scon->real_renderer || !scon->surface) {
+        return;
+    }
+    if (SDL_GetRendererOutputSize(scon->real_renderer, &ow, &oh) != 0) {
+        return;
+    }
+    fits = ow >= surface_width(scon->surface) && oh >= surface_height(scon->surface);
+    if (scon->opts->u.sdl.has_integer_scale && scon->opts->u.sdl.integer_scale) {
+        SDL_RenderSetIntegerScale(scon->real_renderer, fits);
+    }
+    if (scon->texture) {
+        SDL_SetTextureScaleMode(scon->texture, fits ? SDL_ScaleModeNearest : SDL_ScaleModeLinear);
+    }
+}
+
+/*
+ * The guest changed its mode: in a window, show the picture 1:1 and keep the window's centre
+ * where it was, moved as needed to stay on its monitor; a picture bigger than the monitor's
+ * usable area is scaled down to fit it, keeping its aspect ratio.
+ */
 void sdl2_window_resize(struct sdl2_console *scon)
 {
+    int w, h, x, y, ow, oh, aw, ah, top = 0, left = 0, bottom = 0, right = 0, d;
+    SDL_Rect area;
+
     if (!scon->real_window) {
         return;
     }
-
-    SDL_SetWindowSize(scon->real_window,
-                      surface_width(scon->surface),
-                      surface_height(scon->surface));
+    w = surface_width(scon->surface);
+    h = surface_height(scon->surface);
+    d = SDL_GetWindowDisplayIndex(scon->real_window);
+    if ((SDL_GetWindowFlags(scon->real_window) & SDL_WINDOW_FULLSCREEN) || d < 0 ||
+        SDL_GetDisplayUsableBounds(d, &area) != 0) {
+        SDL_SetWindowSize(scon->real_window, w, h);
+        sdl2_update_scaling(scon);
+        return;
+    }
+    SDL_GetWindowBordersSize(scon->real_window, &top, &left, &bottom, &right);
+    aw = area.w - left - right;
+    ah = area.h - top - bottom;
+    if (w > aw || h > ah) {
+        double s = MIN((double)aw / w, (double)ah / h);
+        w = MAX(1, (int)(w * s));
+        h = MAX(1, (int)(h * s));
+    }
+    SDL_GetWindowPosition(scon->real_window, &x, &y);
+    SDL_GetWindowSize(scon->real_window, &ow, &oh);
+    x += (ow - w) / 2;
+    y += (oh - h) / 2;
+    x = MAX(MIN(x, area.x + area.w - right - w), area.x + left);
+    y = MAX(MIN(y, area.y + area.h - bottom - h), area.y + top);
+    SDL_SetWindowSize(scon->real_window, w, h);
+    SDL_SetWindowPosition(scon->real_window, x, y);
+    sdl2_update_scaling(scon);
 }
 
 static void sdl2_redraw(struct sdl2_console *scon)
@@ -238,14 +400,13 @@ static void sdl_show_cursor(struct sdl2_console *scon)
         SDL_SetRelativeMouseMode(SDL_FALSE);
     }
 
+    SDL_ShowCursor(SDL_ENABLE);
     if (guest_cursor &&
         (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled)) {
-        SDL_SetCursor(guest_sprite);
+        sdl_set_guest_cursor();
     } else {
         SDL_SetCursor(sdl_cursor_normal);
     }
-
-    SDL_ShowCursor(SDL_ENABLE);
 }
 
 static void sdl_grab_start(struct sdl2_console *scon)
@@ -264,7 +425,7 @@ static void sdl_grab_start(struct sdl2_console *scon)
         return;
     }
     if (guest_cursor) {
-        SDL_SetCursor(guest_sprite);
+        sdl_set_guest_cursor();
         if (!qemu_input_is_absolute(scon->dcl.con) && !absolute_enabled) {
             SDL_WarpMouseInWindow(scon->real_window, guest_x, guest_y);
         }
@@ -362,7 +523,9 @@ static void toggle_full_screen(struct sdl2_console *scon)
             sdl_grab_end(scon);
         }
         SDL_SetWindowFullscreen(scon->real_window, 0);
+        sdl2_window_resize(scon);       /* the guest may have changed modes meanwhile */
     }
+    sdl2_update_scaling(scon);
     sdl2_redraw(scon);
 }
 
@@ -495,6 +658,35 @@ static void handle_textinput(SDL_Event *ev)
     }
 }
 
+/*
+ * Window coordinates of a mouse event -> guest pixels. The 2D renderer scales the picture
+ * with SDL_RenderSetLogicalSize, and SDL then already reports mouse events in logical
+ * coordinates (= guest pixels, letterbox removed); only the OpenGL path has to scale.
+ */
+static bool sdl_mouse_is_logical(struct sdl2_console *scon)
+{
+    return !scon->opengl && scon->real_renderer;
+}
+
+static void sdl_window_to_guest(struct sdl2_console *scon, int wx, int wy,
+                                int *gx, int *gy)
+{
+    int surf_w = surface_width(scon->surface);
+    int surf_h = surface_height(scon->surface);
+    int scr_w, scr_h;
+
+    if (sdl_mouse_is_logical(scon)) {
+        *gx = wx;
+        *gy = wy;
+    } else {
+        SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
+        *gx = (int64_t)wx * surf_w / scr_w;
+        *gy = (int64_t)wy * surf_h / scr_h;
+    }
+    *gx = MAX(0, MIN(*gx, surf_w - 1));
+    *gy = MAX(0, MIN(*gy, surf_h - 1));
+}
+
 static void handle_mousemotion(SDL_Event *ev)
 {
     int max_x, max_y;
@@ -507,25 +699,32 @@ static void handle_mousemotion(SDL_Event *ev)
 
     SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
     if (qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) {
+        /* the window edges, in window coordinates */
+        int wx = ev->motion.x, wy = ev->motion.y;
+        if (sdl_mouse_is_logical(scon)) {
+            SDL_GetMouseState(&wx, &wy);
+        }
         max_x = scr_w - 1;
         max_y = scr_h - 1;
         if (gui_grab && !gui_fullscreen
-            && (ev->motion.x == 0 || ev->motion.y == 0 ||
-                ev->motion.x == max_x || ev->motion.y == max_y)) {
+            && (wx <= 0 || wy <= 0 || wx >= max_x || wy >= max_y)) {
             sdl_grab_end(scon);
         }
         if (!gui_grab &&
-            (ev->motion.x > 0 && ev->motion.x < max_x &&
-             ev->motion.y > 0 && ev->motion.y < max_y)) {
+            (wx > 0 && wx < max_x && wy > 0 && wy < max_y)) {
             sdl_grab_start(scon);
         }
     }
     surf_w = surface_width(scon->surface);
     surf_h = surface_height(scon->surface);
-    x = (int64_t)ev->motion.x * surf_w / scr_w;
-    y = (int64_t)ev->motion.y * surf_h / scr_h;
-    dx = (int64_t)ev->motion.xrel * surf_w / scr_w;
-    dy = (int64_t)ev->motion.yrel * surf_h / scr_h;
+    sdl_window_to_guest(scon, ev->motion.x, ev->motion.y, &x, &y);
+    if (sdl_mouse_is_logical(scon)) {
+        dx = ev->motion.xrel;
+        dy = ev->motion.yrel;
+    } else {
+        dx = (int64_t)ev->motion.xrel * surf_w / scr_w;
+        dy = (int64_t)ev->motion.yrel * surf_h / scr_h;
+    }
     if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) {
         sdl_send_mouse_event(scon, dx, dy, x, y, ev->motion.state);
     }
@@ -536,16 +735,14 @@ static void handle_mousebutton(SDL_Event *ev)
     int buttonstate = SDL_GetMouseState(NULL, NULL);
     SDL_MouseButtonEvent *bev;
     struct sdl2_console *scon = get_scon_from_window(ev->button.windowID);
-    int scr_w, scr_h, x, y;
+    int x, y;
 
     if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
         return;
     }
 
     bev = &ev->button;
-    SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
-    x = (int64_t)bev->x * surface_width(scon->surface) / scr_w;
-    y = (int64_t)bev->y * surface_height(scon->surface) / scr_h;
+    sdl_window_to_guest(scon, bev->x, bev->y, &x, &y);
 
     if (!gui_grab && !qemu_input_is_absolute(scon->dcl.con)) {
         if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) {
@@ -608,11 +805,17 @@ static void handle_windowevent(SDL_Event *ev)
             };
             qemu_console_set_ui_info(scon->dcl.con, &info, true);
         }
+        sdl2_update_scaling(scon);
+        sdl_rescale_guest_sprite(scon);
         sdl2_redraw(scon);
         break;
     case SDL_WINDOWEVENT_EXPOSED:
         sdl2_redraw(scon);
         break;
+    case SDL_WINDOWEVENT_SIZE_CHANGED:
+        sdl2_update_scaling(scon);
+        sdl_rescale_guest_sprite(scon);
+        break;
     case SDL_WINDOWEVENT_FOCUS_GAINED:
         /* fall through */
     case SDL_WINDOWEVENT_ENTER:
@@ -730,6 +933,106 @@ void sdl2_poll_events(struct sdl2_console *scon)
     }
 }
 
+/*
+ * The guest's cursor is shown as the host mouse pointer. Scale it like the picture (with
+ * integer-scale=on, full screen is an exact multiple), so it looks and lines up the same.
+ */
+static int sdl_cursor_scale(struct sdl2_console *scon)
+{
+    float sx = 1.0f, sy = 1.0f;
+    int scale;
+
+    if (scon && sdl_mouse_is_logical(scon)) {
+        SDL_RenderGetScale(scon->real_renderer, &sx, &sy);
+    }
+    scale = (int)(MIN(sx, sy) + 0.5f);
+    return MAX(1, MIN(scale, 8));
+}
+
+static void sdl_build_sdl_sprite(struct sdl2_console *scon)
+{
+    QEMUCursor *c = guest_cursor_def;
+    int scale = sdl_cursor_scale(scon);
+    int x, y, i, j;
+
+    g_clear_pointer(&guest_sprite, SDL_FreeCursor);
+    g_clear_pointer(&guest_sprite_surface, SDL_FreeSurface);
+    if (scale != guest_sprite_scale && c) {
+        info_report("sdl2: guest cursor %ux%u shown at %dx", c->width, c->height, scale);
+    }
+    guest_sprite_scale = scale;
+    if (!c) {
+        return;
+    }
+
+    guest_sprite_surface =
+        SDL_CreateRGBSurface(0, c->width * scale, c->height * scale, 32,
+                             0xff0000, 0x00ff00, 0xff, 0xff000000);
+    if (!guest_sprite_surface) {
+        fprintf(stderr, "Failed to make rgb surface from %p\n", c);
+        return;
+    }
+    SDL_LockSurface(guest_sprite_surface);
+    for (y = 0; y < c->height; y++) {
+        for (j = 0; j < scale; j++) {
+            uint32_t *dst = (uint32_t *)((uint8_t *)guest_sprite_surface->pixels +
+                                         (y * scale + j) * guest_sprite_surface->pitch);
+            for (x = 0; x < c->width; x++) {
+                for (i = 0; i < scale; i++) {
+                    *dst++ = c->data[y * c->width + x];
+                }
+            }
+        }
+    }
+    SDL_UnlockSurface(guest_sprite_surface);
+    guest_sprite = SDL_CreateColorCursor(guest_sprite_surface,
+                                         c->hot_x * scale, c->hot_y * scale);
+    if (!guest_sprite) {
+        fprintf(stderr, "Failed to make color cursor from %p\n", c);
+    }
+}
+
+static void sdl_build_guest_sprite(struct sdl2_console *scon)
+{
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+    HCURSOR old_hcursor = guest_hcursor;
+    QEMUCursor *c = guest_cursor_def;
+
+    guest_hcursor = NULL;
+#endif
+    sdl_build_sdl_sprite(scon);
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+    if (guest_sprite) {
+        guest_hcursor = sdl_create_hcursor(guest_sprite_surface,
+                                           c->hot_x * guest_sprite_scale,
+                                           c->hot_y * guest_sprite_scale);
+    }
+    if (old_hcursor) {
+        if (GetCursor() == old_hcursor) {
+            SetCursor(guest_hcursor);   /* NULL: none until SDL sets its next cursor */
+        }
+        DestroyCursor(old_hcursor);
+    }
+#endif
+}
+
+static void sdl_apply_guest_sprite(struct sdl2_console *scon)
+{
+    if (guest_sprite && guest_cursor &&
+        (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled)) {
+        sdl_set_guest_cursor();
+    }
+}
+
+/* the window was resized or went (out of) full screen */
+static void sdl_rescale_guest_sprite(struct sdl2_console *scon)
+{
+    if (guest_cursor_def && sdl_cursor_scale(scon) != guest_sprite_scale) {
+        sdl_build_guest_sprite(scon);
+        sdl_apply_guest_sprite(scon);
+    }
+}
+
 static void sdl_mouse_warp(DisplayChangeListener *dcl,
                            int x, int y, bool on)
 {
@@ -738,13 +1041,14 @@ static void sdl_mouse_warp(DisplayChangeListener *dcl,
     if (!qemu_console_is_graphic(scon->dcl.con)) {
         return;
     }
+    sdl_rescale_guest_sprite(scon);
 
     if (on) {
         if (!guest_cursor) {
             sdl_show_cursor(scon);
         }
         if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) {
-            SDL_SetCursor(guest_sprite);
+            sdl_set_guest_cursor();
             if (!qemu_input_is_absolute(scon->dcl.con) && !absolute_enabled) {
                 SDL_WarpMouseInWindow(scon->real_window, x, y);
             }
@@ -759,33 +1063,15 @@ static void sdl_mouse_warp(DisplayChangeListener *dcl,
 static void sdl_mouse_define(DisplayChangeListener *dcl,
                              QEMUCursor *c)
 {
+    struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
 
-    if (guest_sprite) {
-        SDL_FreeCursor(guest_sprite);
-    }
-
-    if (guest_sprite_surface) {
-        SDL_FreeSurface(guest_sprite_surface);
-    }
-
-    guest_sprite_surface =
-        SDL_CreateRGBSurfaceFrom(c->data, c->width, c->height, 32, c->width * 4,
-                                 0xff0000, 0x00ff00, 0xff, 0xff000000);
-
-    if (!guest_sprite_surface) {
-        fprintf(stderr, "Failed to make rgb surface from %p\n", c);
-        return;
-    }
-    guest_sprite = SDL_CreateColorCursor(guest_sprite_surface,
-                                         c->hot_x, c->hot_y);
-    if (!guest_sprite) {
-        fprintf(stderr, "Failed to make color cursor from %p\n", c);
-        return;
-    }
-    if (guest_cursor &&
-        (gui_grab || qemu_input_is_absolute(dcl->con) || absolute_enabled)) {
-        SDL_SetCursor(guest_sprite);
+    cursor_ref(c);
+    if (guest_cursor_def) {
+        cursor_unref(guest_cursor_def);
     }
+    guest_cursor_def = c;
+    sdl_build_guest_sprite(scon);
+    sdl_apply_guest_sprite(scon);
 }
 
 static void sdl_cleanup(void)
@@ -808,7 +1094,14 @@ static void sdl_cleanup(void)
 
     g_clear_pointer(&guest_sprite, SDL_FreeCursor);
     g_clear_pointer(&guest_sprite_surface, SDL_FreeSurface);
+    g_clear_pointer(&guest_cursor_def, cursor_unref);
     g_clear_pointer(&sdl_cursor_hidden, SDL_FreeCursor);
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+    if (guest_hcursor) {
+        DestroyCursor(guest_hcursor);
+        guest_hcursor = NULL;
+    }
+#endif
     SDL_QuitSubSystem(SDL_INIT_VIDEO);
 }
 
