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