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