qemu/hw/display/milkymist-tmu2.c
<<
>>
Prefs
   1/*
   2 *  QEMU model of the Milkymist texture mapping unit.
   3 *
   4 *  Copyright (c) 2010 Michael Walle <michael@walle.cc>
   5 *  Copyright (c) 2010 Sebastien Bourdeauducq
   6 *                       <sebastien.bourdeauducq@lekernel.net>
   7 *
   8 * This library is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU Lesser General Public
  10 * License as published by the Free Software Foundation; either
  11 * version 2.1 of the License, or (at your option) any later version.
  12 *
  13 * This library is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16 * Lesser General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU Lesser General Public
  19 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  20 *
  21 *
  22 * Specification available at:
  23 *   http://milkymist.walle.cc/socdoc/tmu2.pdf
  24 *
  25 */
  26
  27#include "qemu/osdep.h"
  28#include "hw/irq.h"
  29#include "hw/sysbus.h"
  30#include "migration/vmstate.h"
  31#include "trace.h"
  32#include "qapi/error.h"
  33#include "qemu/error-report.h"
  34#include "qemu/module.h"
  35#include "qapi/error.h"
  36#include "hw/display/milkymist_tmu2.h"
  37
  38#include <X11/Xlib.h>
  39#include <epoxy/gl.h>
  40#include <epoxy/glx.h>
  41#include "qom/object.h"
  42
  43enum {
  44    R_CTL = 0,
  45    R_HMESHLAST,
  46    R_VMESHLAST,
  47    R_BRIGHTNESS,
  48    R_CHROMAKEY,
  49    R_VERTICESADDR,
  50    R_TEXFBUF,
  51    R_TEXHRES,
  52    R_TEXVRES,
  53    R_TEXHMASK,
  54    R_TEXVMASK,
  55    R_DSTFBUF,
  56    R_DSTHRES,
  57    R_DSTVRES,
  58    R_DSTHOFFSET,
  59    R_DSTVOFFSET,
  60    R_DSTSQUAREW,
  61    R_DSTSQUAREH,
  62    R_ALPHA,
  63    R_MAX
  64};
  65
  66enum {
  67    CTL_START_BUSY  = (1<<0),
  68    CTL_CHROMAKEY   = (1<<1),
  69};
  70
  71enum {
  72    MAX_BRIGHTNESS = 63,
  73    MAX_ALPHA      = 63,
  74};
  75
  76enum {
  77    MESH_MAXSIZE = 128,
  78};
  79
  80struct vertex {
  81    int x;
  82    int y;
  83} QEMU_PACKED;
  84
  85#define TYPE_MILKYMIST_TMU2 "milkymist-tmu2"
  86OBJECT_DECLARE_SIMPLE_TYPE(MilkymistTMU2State, MILKYMIST_TMU2)
  87
  88struct MilkymistTMU2State {
  89    SysBusDevice parent_obj;
  90
  91    MemoryRegion regs_region;
  92    Chardev *chr;
  93    qemu_irq irq;
  94
  95    uint32_t regs[R_MAX];
  96
  97    Display *dpy;
  98    GLXFBConfig glx_fb_config;
  99    GLXContext glx_context;
 100};
 101
 102static const int glx_fbconfig_attr[] = {
 103    GLX_GREEN_SIZE, 5,
 104    GLX_GREEN_SIZE, 6,
 105    GLX_BLUE_SIZE, 5,
 106    None
 107};
 108
 109static int tmu2_glx_init(MilkymistTMU2State *s)
 110{
 111    GLXFBConfig *configs;
 112    int nelements;
 113
 114    s->dpy = XOpenDisplay(NULL); /* FIXME: call XCloseDisplay() */
 115    if (s->dpy == NULL) {
 116        return 1;
 117    }
 118
 119    configs = glXChooseFBConfig(s->dpy, 0, glx_fbconfig_attr, &nelements);
 120    if (configs == NULL) {
 121        return 1;
 122    }
 123
 124    s->glx_fb_config = *configs;
 125    XFree(configs);
 126
 127    /* FIXME: call glXDestroyContext() */
 128    s->glx_context = glXCreateNewContext(s->dpy, s->glx_fb_config,
 129            GLX_RGBA_TYPE, NULL, 1);
 130    if (s->glx_context == NULL) {
 131        return 1;
 132    }
 133
 134    return 0;
 135}
 136
 137static void tmu2_gl_map(struct vertex *mesh, int texhres, int texvres,
 138        int hmeshlast, int vmeshlast, int ho, int vo, int sw, int sh)
 139{
 140    int x, y;
 141    int x0, y0, x1, y1;
 142    int u0, v0, u1, v1, u2, v2, u3, v3;
 143    double xscale = 1.0 / ((double)(64 * texhres));
 144    double yscale = 1.0 / ((double)(64 * texvres));
 145
 146    glLoadIdentity();
 147    glTranslatef(ho, vo, 0);
 148    glEnable(GL_TEXTURE_2D);
 149    glBegin(GL_QUADS);
 150
 151    for (y = 0; y < vmeshlast; y++) {
 152        y0 = y * sh;
 153        y1 = y0 + sh;
 154        for (x = 0; x < hmeshlast; x++) {
 155            x0 = x * sw;
 156            x1 = x0 + sw;
 157
 158            u0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].x);
 159            v0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].y);
 160            u1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].x);
 161            v1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].y);
 162            u2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].x);
 163            v2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].y);
 164            u3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].x);
 165            v3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].y);
 166
 167            glTexCoord2d(((double)u0) * xscale, ((double)v0) * yscale);
 168            glVertex3i(x0, y0, 0);
 169            glTexCoord2d(((double)u1) * xscale, ((double)v1) * yscale);
 170            glVertex3i(x1, y0, 0);
 171            glTexCoord2d(((double)u2) * xscale, ((double)v2) * yscale);
 172            glVertex3i(x1, y1, 0);
 173            glTexCoord2d(((double)u3) * xscale, ((double)v3) * yscale);
 174            glVertex3i(x0, y1, 0);
 175        }
 176    }
 177
 178    glEnd();
 179}
 180
 181static void tmu2_start(MilkymistTMU2State *s)
 182{
 183    int pbuffer_attrib[6] = {
 184        GLX_PBUFFER_WIDTH,
 185        0,
 186        GLX_PBUFFER_HEIGHT,
 187        0,
 188        GLX_PRESERVED_CONTENTS,
 189        True
 190    };
 191
 192    GLXPbuffer pbuffer;
 193    GLuint texture;
 194    void *fb;
 195    hwaddr fb_len;
 196    void *mesh;
 197    hwaddr mesh_len;
 198    float m;
 199
 200    trace_milkymist_tmu2_start();
 201
 202    /* Create and set up a suitable OpenGL context */
 203    pbuffer_attrib[1] = s->regs[R_DSTHRES];
 204    pbuffer_attrib[3] = s->regs[R_DSTVRES];
 205    pbuffer = glXCreatePbuffer(s->dpy, s->glx_fb_config, pbuffer_attrib);
 206    glXMakeContextCurrent(s->dpy, pbuffer, pbuffer, s->glx_context);
 207
 208    /* Fixup endianness. TODO: would it work on BE hosts? */
 209    glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
 210    glPixelStorei(GL_PACK_SWAP_BYTES, 1);
 211
 212    /* Row alignment */
 213    glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
 214    glPixelStorei(GL_PACK_ALIGNMENT, 2);
 215
 216    /* Read the QEMU source framebuffer into an OpenGL texture */
 217    glGenTextures(1, &texture);
 218    glBindTexture(GL_TEXTURE_2D, texture);
 219    fb_len = 2ULL * s->regs[R_TEXHRES] * s->regs[R_TEXVRES];
 220    fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, false);
 221    if (fb == NULL) {
 222        glDeleteTextures(1, &texture);
 223        glXMakeContextCurrent(s->dpy, None, None, NULL);
 224        glXDestroyPbuffer(s->dpy, pbuffer);
 225        return;
 226    }
 227    glTexImage2D(GL_TEXTURE_2D, 0, 3, s->regs[R_TEXHRES], s->regs[R_TEXVRES],
 228            0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fb);
 229    cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
 230
 231    /* Set up texturing options */
 232    /* WARNING:
 233     * Many cases of TMU2 masking are not supported by OpenGL.
 234     * We only implement the most common ones:
 235     *  - full bilinear filtering vs. nearest texel
 236     *  - texture clamping vs. texture wrapping
 237     */
 238    if ((s->regs[R_TEXHMASK] & 0x3f) > 0x20) {
 239        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 240        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 241    } else {
 242        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 243        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 244    }
 245    if ((s->regs[R_TEXHMASK] >> 6) & s->regs[R_TEXHRES]) {
 246        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
 247    } else {
 248        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 249    }
 250    if ((s->regs[R_TEXVMASK] >> 6) & s->regs[R_TEXVRES]) {
 251        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
 252    } else {
 253        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 254    }
 255
 256    /* Translucency and decay */
 257    glEnable(GL_BLEND);
 258    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 259    m = (float)(s->regs[R_BRIGHTNESS] + 1) / 64.0f;
 260    glColor4f(m, m, m, (float)(s->regs[R_ALPHA] + 1) / 64.0f);
 261
 262    /* Read the QEMU dest. framebuffer into the OpenGL framebuffer */
 263    fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
 264    fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, false);
 265    if (fb == NULL) {
 266        glDeleteTextures(1, &texture);
 267        glXMakeContextCurrent(s->dpy, None, None, NULL);
 268        glXDestroyPbuffer(s->dpy, pbuffer);
 269        return;
 270    }
 271
 272    glDrawPixels(s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
 273            GL_UNSIGNED_SHORT_5_6_5, fb);
 274    cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
 275    glViewport(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES]);
 276    glMatrixMode(GL_PROJECTION);
 277    glLoadIdentity();
 278    glOrtho(0.0, s->regs[R_DSTHRES], 0.0, s->regs[R_DSTVRES], -1.0, 1.0);
 279    glMatrixMode(GL_MODELVIEW);
 280
 281    /* Map the texture */
 282    mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex);
 283    mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, false);
 284    if (mesh == NULL) {
 285        glDeleteTextures(1, &texture);
 286        glXMakeContextCurrent(s->dpy, None, None, NULL);
 287        glXDestroyPbuffer(s->dpy, pbuffer);
 288        return;
 289    }
 290
 291    tmu2_gl_map((struct vertex *)mesh,
 292        s->regs[R_TEXHRES], s->regs[R_TEXVRES],
 293        s->regs[R_HMESHLAST], s->regs[R_VMESHLAST],
 294        s->regs[R_DSTHOFFSET], s->regs[R_DSTVOFFSET],
 295        s->regs[R_DSTSQUAREW], s->regs[R_DSTSQUAREH]);
 296    cpu_physical_memory_unmap(mesh, mesh_len, 0, mesh_len);
 297
 298    /* Write back the OpenGL framebuffer to the QEMU framebuffer */
 299    fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
 300    fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, true);
 301    if (fb == NULL) {
 302        glDeleteTextures(1, &texture);
 303        glXMakeContextCurrent(s->dpy, None, None, NULL);
 304        glXDestroyPbuffer(s->dpy, pbuffer);
 305        return;
 306    }
 307
 308    glReadPixels(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
 309            GL_UNSIGNED_SHORT_5_6_5, fb);
 310    cpu_physical_memory_unmap(fb, fb_len, 1, fb_len);
 311
 312    /* Free OpenGL allocs */
 313    glDeleteTextures(1, &texture);
 314    glXMakeContextCurrent(s->dpy, None, None, NULL);
 315    glXDestroyPbuffer(s->dpy, pbuffer);
 316
 317    s->regs[R_CTL] &= ~CTL_START_BUSY;
 318
 319    trace_milkymist_tmu2_pulse_irq();
 320    qemu_irq_pulse(s->irq);
 321}
 322
 323static uint64_t tmu2_read(void *opaque, hwaddr addr,
 324                          unsigned size)
 325{
 326    MilkymistTMU2State *s = opaque;
 327    uint32_t r = 0;
 328
 329    addr >>= 2;
 330    switch (addr) {
 331    case R_CTL:
 332    case R_HMESHLAST:
 333    case R_VMESHLAST:
 334    case R_BRIGHTNESS:
 335    case R_CHROMAKEY:
 336    case R_VERTICESADDR:
 337    case R_TEXFBUF:
 338    case R_TEXHRES:
 339    case R_TEXVRES:
 340    case R_TEXHMASK:
 341    case R_TEXVMASK:
 342    case R_DSTFBUF:
 343    case R_DSTHRES:
 344    case R_DSTVRES:
 345    case R_DSTHOFFSET:
 346    case R_DSTVOFFSET:
 347    case R_DSTSQUAREW:
 348    case R_DSTSQUAREH:
 349    case R_ALPHA:
 350        r = s->regs[addr];
 351        break;
 352
 353    default:
 354        error_report("milkymist_tmu2: read access to unknown register 0x"
 355                TARGET_FMT_plx, addr << 2);
 356        break;
 357    }
 358
 359    trace_milkymist_tmu2_memory_read(addr << 2, r);
 360
 361    return r;
 362}
 363
 364static void tmu2_check_registers(MilkymistTMU2State *s)
 365{
 366    if (s->regs[R_BRIGHTNESS] > MAX_BRIGHTNESS) {
 367        error_report("milkymist_tmu2: max brightness is %d", MAX_BRIGHTNESS);
 368    }
 369
 370    if (s->regs[R_ALPHA] > MAX_ALPHA) {
 371        error_report("milkymist_tmu2: max alpha is %d", MAX_ALPHA);
 372    }
 373
 374    if (s->regs[R_VERTICESADDR] & 0x07) {
 375        error_report("milkymist_tmu2: vertex mesh address has to be 64-bit "
 376                "aligned");
 377    }
 378
 379    if (s->regs[R_TEXFBUF] & 0x01) {
 380        error_report("milkymist_tmu2: texture buffer address has to be "
 381                "16-bit aligned");
 382    }
 383}
 384
 385static void tmu2_write(void *opaque, hwaddr addr, uint64_t value,
 386                       unsigned size)
 387{
 388    MilkymistTMU2State *s = opaque;
 389
 390    trace_milkymist_tmu2_memory_write(addr, value);
 391
 392    addr >>= 2;
 393    switch (addr) {
 394    case R_CTL:
 395        s->regs[addr] = value;
 396        if (value & CTL_START_BUSY) {
 397            tmu2_start(s);
 398        }
 399        break;
 400    case R_BRIGHTNESS:
 401    case R_HMESHLAST:
 402    case R_VMESHLAST:
 403    case R_CHROMAKEY:
 404    case R_VERTICESADDR:
 405    case R_TEXFBUF:
 406    case R_TEXHRES:
 407    case R_TEXVRES:
 408    case R_TEXHMASK:
 409    case R_TEXVMASK:
 410    case R_DSTFBUF:
 411    case R_DSTHRES:
 412    case R_DSTVRES:
 413    case R_DSTHOFFSET:
 414    case R_DSTVOFFSET:
 415    case R_DSTSQUAREW:
 416    case R_DSTSQUAREH:
 417    case R_ALPHA:
 418        s->regs[addr] = value;
 419        break;
 420
 421    default:
 422        error_report("milkymist_tmu2: write access to unknown register 0x"
 423                TARGET_FMT_plx, addr << 2);
 424        break;
 425    }
 426
 427    tmu2_check_registers(s);
 428}
 429
 430static const MemoryRegionOps tmu2_mmio_ops = {
 431    .read = tmu2_read,
 432    .write = tmu2_write,
 433    .valid = {
 434        .min_access_size = 4,
 435        .max_access_size = 4,
 436    },
 437    .endianness = DEVICE_NATIVE_ENDIAN,
 438};
 439
 440static void milkymist_tmu2_reset(DeviceState *d)
 441{
 442    MilkymistTMU2State *s = MILKYMIST_TMU2(d);
 443    int i;
 444
 445    for (i = 0; i < R_MAX; i++) {
 446        s->regs[i] = 0;
 447    }
 448}
 449
 450static void milkymist_tmu2_init(Object *obj)
 451{
 452    MilkymistTMU2State *s = MILKYMIST_TMU2(obj);
 453    SysBusDevice *dev = SYS_BUS_DEVICE(obj);
 454
 455    sysbus_init_irq(dev, &s->irq);
 456
 457    memory_region_init_io(&s->regs_region, obj, &tmu2_mmio_ops, s,
 458            "milkymist-tmu2", R_MAX * 4);
 459    sysbus_init_mmio(dev, &s->regs_region);
 460}
 461
 462static void milkymist_tmu2_realize(DeviceState *dev, Error **errp)
 463{
 464    MilkymistTMU2State *s = MILKYMIST_TMU2(dev);
 465
 466    if (tmu2_glx_init(s)) {
 467        error_setg(errp, "tmu2_glx_init failed");
 468    }
 469}
 470
 471static const VMStateDescription vmstate_milkymist_tmu2 = {
 472    .name = "milkymist-tmu2",
 473    .version_id = 1,
 474    .minimum_version_id = 1,
 475    .fields = (VMStateField[]) {
 476        VMSTATE_UINT32_ARRAY(regs, MilkymistTMU2State, R_MAX),
 477        VMSTATE_END_OF_LIST()
 478    }
 479};
 480
 481static void milkymist_tmu2_class_init(ObjectClass *klass, void *data)
 482{
 483    DeviceClass *dc = DEVICE_CLASS(klass);
 484
 485    dc->realize = milkymist_tmu2_realize;
 486    dc->reset = milkymist_tmu2_reset;
 487    dc->vmsd = &vmstate_milkymist_tmu2;
 488}
 489
 490static const TypeInfo milkymist_tmu2_info = {
 491    .name          = TYPE_MILKYMIST_TMU2,
 492    .parent        = TYPE_SYS_BUS_DEVICE,
 493    .instance_size = sizeof(MilkymistTMU2State),
 494    .instance_init = milkymist_tmu2_init,
 495    .class_init    = milkymist_tmu2_class_init,
 496};
 497
 498static void milkymist_tmu2_register_types(void)
 499{
 500    type_register_static(&milkymist_tmu2_info);
 501}
 502
 503type_init(milkymist_tmu2_register_types)
 504
 505DeviceState *milkymist_tmu2_create(hwaddr base, qemu_irq irq)
 506{
 507    DeviceState *dev;
 508    Display *d;
 509    GLXFBConfig *configs;
 510    int nelements;
 511    int ver_major, ver_minor;
 512
 513    /* check that GLX will work */
 514    d = XOpenDisplay(NULL);
 515    if (d == NULL) {
 516        return NULL;
 517    }
 518
 519    if (!glXQueryVersion(d, &ver_major, &ver_minor)) {
 520        /*
 521         * Yeah, sometimes getting the GLX version can fail.
 522         * Isn't X beautiful?
 523         */
 524        XCloseDisplay(d);
 525        return NULL;
 526    }
 527
 528    if ((ver_major < 1) || ((ver_major == 1) && (ver_minor < 3))) {
 529        printf("Your GLX version is %d.%d,"
 530          "but TMU emulation needs at least 1.3. TMU disabled.\n",
 531          ver_major, ver_minor);
 532        XCloseDisplay(d);
 533        return NULL;
 534    }
 535
 536    configs = glXChooseFBConfig(d, 0, glx_fbconfig_attr, &nelements);
 537    if (configs == NULL) {
 538        XCloseDisplay(d);
 539        return NULL;
 540    }
 541
 542    XFree(configs);
 543    XCloseDisplay(d);
 544
 545    dev = qdev_new(TYPE_MILKYMIST_TMU2);
 546    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
 547    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
 548    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
 549
 550    return dev;
 551}
 552