static void tmu2_start(MilkymistTMU2State *s)

{

    int pbuffer_attrib[6] = {

        GLX_PBUFFER_WIDTH,

        0,

        GLX_PBUFFER_HEIGHT,

        0,

        GLX_PRESERVED_CONTENTS,

        True

    };



    GLXPbuffer pbuffer;

    GLuint texture;

    void *fb;

    target_phys_addr_t fb_len;

    void *mesh;

    target_phys_addr_t mesh_len;

    float m;



    trace_milkymist_tmu2_start();



    /* Create and set up a suitable OpenGL context */

    pbuffer_attrib[1] = s->regs[R_DSTHRES];

    pbuffer_attrib[3] = s->regs[R_DSTVRES];

    pbuffer = glXCreatePbuffer(s->dpy, s->glx_fb_config, pbuffer_attrib);

    glXMakeContextCurrent(s->dpy, pbuffer, pbuffer, s->glx_context);



    /* Fixup endianness. TODO: would it work on BE hosts? */

    glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);

    glPixelStorei(GL_PACK_SWAP_BYTES, 1);



    /* Row alignment */

    glPixelStorei(GL_UNPACK_ALIGNMENT, 2);

    glPixelStorei(GL_PACK_ALIGNMENT, 2);



    /* Read the QEMU source framebuffer into an OpenGL texture */

    glGenTextures(1, &texture);

    glBindTexture(GL_TEXTURE_2D, texture);

    fb_len = 2*s->regs[R_TEXHRES]*s->regs[R_TEXVRES];

    fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, 0);

    if (fb == NULL) {

        glDeleteTextures(1, &texture);

        glXMakeContextCurrent(s->dpy, None, None, NULL);

        glXDestroyPbuffer(s->dpy, pbuffer);

        return;

    }

    glTexImage2D(GL_TEXTURE_2D, 0, 3, s->regs[R_TEXHRES], s->regs[R_TEXVRES],

            0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fb);

    cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);



    /* Set up texturing options */

    /* WARNING:

     * Many cases of TMU2 masking are not supported by OpenGL.

     * We only implement the most common ones:

     *  - full bilinear filtering vs. nearest texel

     *  - texture clamping vs. texture wrapping

     */

    if ((s->regs[R_TEXHMASK] & 0x3f) > 0x20) {

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    } else {

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    }

    if ((s->regs[R_TEXHMASK] >> 6) & s->regs[R_TEXHRES]) {

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

    } else {

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    }

    if ((s->regs[R_TEXVMASK] >> 6) & s->regs[R_TEXVRES]) {

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    } else {

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    }



    /* Translucency and decay */

    glEnable(GL_BLEND);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    m = (float)(s->regs[R_BRIGHTNESS] + 1) / 64.0f;

    glColor4f(m, m, m, (float)(s->regs[R_ALPHA] + 1) / 64.0f);



    /* Read the QEMU dest. framebuffer into the OpenGL framebuffer */

    fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];

    fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 0);

    if (fb == NULL) {

        glDeleteTextures(1, &texture);

        glXMakeContextCurrent(s->dpy, None, None, NULL);

        glXDestroyPbuffer(s->dpy, pbuffer);

        return;

    }



    glDrawPixels(s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,

            GL_UNSIGNED_SHORT_5_6_5, fb);

    cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);

    glViewport(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES]);

    glMatrixMode(GL_PROJECTION);

    glLoadIdentity();

    glOrtho(0.0, s->regs[R_DSTHRES], 0.0, s->regs[R_DSTVRES], -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);



    /* Map the texture */

    mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex);

    mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, 0);

    if (mesh == NULL) {

        glDeleteTextures(1, &texture);

        glXMakeContextCurrent(s->dpy, None, None, NULL);

        glXDestroyPbuffer(s->dpy, pbuffer);

        return;

    }



    tmu2_gl_map((struct vertex *)mesh,

        s->regs[R_TEXHRES], s->regs[R_TEXVRES],

        s->regs[R_HMESHLAST], s->regs[R_VMESHLAST],

        s->regs[R_DSTHOFFSET], s->regs[R_DSTVOFFSET],

        s->regs[R_DSTSQUAREW], s->regs[R_DSTSQUAREH]);

    cpu_physical_memory_unmap(mesh, mesh_len, 0, mesh_len);



    /* Write back the OpenGL framebuffer to the QEMU framebuffer */

    fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];

    fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 1);

    if (fb == NULL) {

        glDeleteTextures(1, &texture);

        glXMakeContextCurrent(s->dpy, None, None, NULL);

        glXDestroyPbuffer(s->dpy, pbuffer);

        return;

    }



    glReadPixels(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,

            GL_UNSIGNED_SHORT_5_6_5, fb);

    cpu_physical_memory_unmap(fb, fb_len, 1, fb_len);



    /* Free OpenGL allocs */

    glDeleteTextures(1, &texture);

    glXMakeContextCurrent(s->dpy, None, None, NULL);

    glXDestroyPbuffer(s->dpy, pbuffer);



    s->regs[R_CTL] &= ~CTL_START_BUSY;



    trace_milkymist_tmu2_pulse_irq();

    qemu_irq_pulse(s->irq);

}
