|
|
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.