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