OSDN Git Service

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