502 lines
14 KiB
C
502 lines
14 KiB
C
/*
|
|
* QEMU model of the Milkymist texture mapping unit.
|
|
*
|
|
* Copyright (c) 2010 Michael Walle <michael@walle.cc>
|
|
* Copyright (c) 2010 Sebastien Bourdeauducq
|
|
* <sebastien.bourdeauducq@lekernel.net>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* Specification available at:
|
|
* http://milkymist.walle.cc/socdoc/tmu2.pdf
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "hw/hw.h"
|
|
#include "hw/sysbus.h"
|
|
#include "trace.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <epoxy/gl.h>
|
|
#include <epoxy/glx.h>
|
|
|
|
enum {
|
|
R_CTL = 0,
|
|
R_HMESHLAST,
|
|
R_VMESHLAST,
|
|
R_BRIGHTNESS,
|
|
R_CHROMAKEY,
|
|
R_VERTICESADDR,
|
|
R_TEXFBUF,
|
|
R_TEXHRES,
|
|
R_TEXVRES,
|
|
R_TEXHMASK,
|
|
R_TEXVMASK,
|
|
R_DSTFBUF,
|
|
R_DSTHRES,
|
|
R_DSTVRES,
|
|
R_DSTHOFFSET,
|
|
R_DSTVOFFSET,
|
|
R_DSTSQUAREW,
|
|
R_DSTSQUAREH,
|
|
R_ALPHA,
|
|
R_MAX
|
|
};
|
|
|
|
enum {
|
|
CTL_START_BUSY = (1<<0),
|
|
CTL_CHROMAKEY = (1<<1),
|
|
};
|
|
|
|
enum {
|
|
MAX_BRIGHTNESS = 63,
|
|
MAX_ALPHA = 63,
|
|
};
|
|
|
|
enum {
|
|
MESH_MAXSIZE = 128,
|
|
};
|
|
|
|
struct vertex {
|
|
int x;
|
|
int y;
|
|
} QEMU_PACKED;
|
|
|
|
#define TYPE_MILKYMIST_TMU2 "milkymist-tmu2"
|
|
#define MILKYMIST_TMU2(obj) \
|
|
OBJECT_CHECK(MilkymistTMU2State, (obj), TYPE_MILKYMIST_TMU2)
|
|
|
|
struct MilkymistTMU2State {
|
|
SysBusDevice parent_obj;
|
|
|
|
MemoryRegion regs_region;
|
|
Chardev *chr;
|
|
qemu_irq irq;
|
|
|
|
uint32_t regs[R_MAX];
|
|
|
|
Display *dpy;
|
|
GLXFBConfig glx_fb_config;
|
|
GLXContext glx_context;
|
|
};
|
|
typedef struct MilkymistTMU2State MilkymistTMU2State;
|
|
|
|
static const int glx_fbconfig_attr[] = {
|
|
GLX_GREEN_SIZE, 5,
|
|
GLX_GREEN_SIZE, 6,
|
|
GLX_BLUE_SIZE, 5,
|
|
None
|
|
};
|
|
|
|
static int tmu2_glx_init(MilkymistTMU2State *s)
|
|
{
|
|
GLXFBConfig *configs;
|
|
int nelements;
|
|
|
|
s->dpy = XOpenDisplay(NULL); /* FIXME: call XCloseDisplay() */
|
|
if (s->dpy == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
configs = glXChooseFBConfig(s->dpy, 0, glx_fbconfig_attr, &nelements);
|
|
if (configs == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
s->glx_fb_config = *configs;
|
|
XFree(configs);
|
|
|
|
/* FIXME: call glXDestroyContext() */
|
|
s->glx_context = glXCreateNewContext(s->dpy, s->glx_fb_config,
|
|
GLX_RGBA_TYPE, NULL, 1);
|
|
if (s->glx_context == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tmu2_gl_map(struct vertex *mesh, int texhres, int texvres,
|
|
int hmeshlast, int vmeshlast, int ho, int vo, int sw, int sh)
|
|
{
|
|
int x, y;
|
|
int x0, y0, x1, y1;
|
|
int u0, v0, u1, v1, u2, v2, u3, v3;
|
|
double xscale = 1.0 / ((double)(64 * texhres));
|
|
double yscale = 1.0 / ((double)(64 * texvres));
|
|
|
|
glLoadIdentity();
|
|
glTranslatef(ho, vo, 0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBegin(GL_QUADS);
|
|
|
|
for (y = 0; y < vmeshlast; y++) {
|
|
y0 = y * sh;
|
|
y1 = y0 + sh;
|
|
for (x = 0; x < hmeshlast; x++) {
|
|
x0 = x * sw;
|
|
x1 = x0 + sw;
|
|
|
|
u0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].x);
|
|
v0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].y);
|
|
u1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].x);
|
|
v1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].y);
|
|
u2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].x);
|
|
v2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].y);
|
|
u3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].x);
|
|
v3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].y);
|
|
|
|
glTexCoord2d(((double)u0) * xscale, ((double)v0) * yscale);
|
|
glVertex3i(x0, y0, 0);
|
|
glTexCoord2d(((double)u1) * xscale, ((double)v1) * yscale);
|
|
glVertex3i(x1, y0, 0);
|
|
glTexCoord2d(((double)u2) * xscale, ((double)v2) * yscale);
|
|
glVertex3i(x1, y1, 0);
|
|
glTexCoord2d(((double)u3) * xscale, ((double)v3) * yscale);
|
|
glVertex3i(x0, y1, 0);
|
|
}
|
|
}
|
|
|
|
glEnd();
|
|
}
|
|
|
|
static void tmu2_start(MilkymistTMU2State *s)
|
|
{
|
|
int pbuffer_attrib[6] = {
|
|
GLX_PBUFFER_WIDTH,
|
|
0,
|
|
GLX_PBUFFER_HEIGHT,
|
|
0,
|
|
GLX_PRESERVED_CONTENTS,
|
|
True
|
|
};
|
|
|
|
GLXPbuffer pbuffer;
|
|
GLuint texture;
|
|
void *fb;
|
|
hwaddr fb_len;
|
|
void *mesh;
|
|
hwaddr mesh_len;
|
|
float m;
|
|
|
|
trace_milkymist_tmu2_start();
|
|
|
|
/* Create and set up a suitable OpenGL context */
|
|
pbuffer_attrib[1] = s->regs[R_DSTHRES];
|
|
pbuffer_attrib[3] = s->regs[R_DSTVRES];
|
|
pbuffer = glXCreatePbuffer(s->dpy, s->glx_fb_config, pbuffer_attrib);
|
|
glXMakeContextCurrent(s->dpy, pbuffer, pbuffer, s->glx_context);
|
|
|
|
/* Fixup endianness. TODO: would it work on BE hosts? */
|
|
glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
|
|
glPixelStorei(GL_PACK_SWAP_BYTES, 1);
|
|
|
|
/* Row alignment */
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 2);
|
|
|
|
/* Read the QEMU source framebuffer into an OpenGL texture */
|
|
glGenTextures(1, &texture);
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
fb_len = 2ULL * s->regs[R_TEXHRES] * s->regs[R_TEXVRES];
|
|
fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, 0);
|
|
if (fb == NULL) {
|
|
glDeleteTextures(1, &texture);
|
|
glXMakeContextCurrent(s->dpy, None, None, NULL);
|
|
glXDestroyPbuffer(s->dpy, pbuffer);
|
|
return;
|
|
}
|
|
glTexImage2D(GL_TEXTURE_2D, 0, 3, s->regs[R_TEXHRES], s->regs[R_TEXVRES],
|
|
0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fb);
|
|
cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
|
|
|
|
/* Set up texturing options */
|
|
/* WARNING:
|
|
* Many cases of TMU2 masking are not supported by OpenGL.
|
|
* We only implement the most common ones:
|
|
* - full bilinear filtering vs. nearest texel
|
|
* - texture clamping vs. texture wrapping
|
|
*/
|
|
if ((s->regs[R_TEXHMASK] & 0x3f) > 0x20) {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
} else {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
}
|
|
if ((s->regs[R_TEXHMASK] >> 6) & s->regs[R_TEXHRES]) {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
} else {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
}
|
|
if ((s->regs[R_TEXVMASK] >> 6) & s->regs[R_TEXVRES]) {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
} else {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
}
|
|
|
|
/* Translucency and decay */
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
m = (float)(s->regs[R_BRIGHTNESS] + 1) / 64.0f;
|
|
glColor4f(m, m, m, (float)(s->regs[R_ALPHA] + 1) / 64.0f);
|
|
|
|
/* Read the QEMU dest. framebuffer into the OpenGL framebuffer */
|
|
fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
|
|
fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 0);
|
|
if (fb == NULL) {
|
|
glDeleteTextures(1, &texture);
|
|
glXMakeContextCurrent(s->dpy, None, None, NULL);
|
|
glXDestroyPbuffer(s->dpy, pbuffer);
|
|
return;
|
|
}
|
|
|
|
glDrawPixels(s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
|
|
GL_UNSIGNED_SHORT_5_6_5, fb);
|
|
cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
|
|
glViewport(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES]);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrtho(0.0, s->regs[R_DSTHRES], 0.0, s->regs[R_DSTVRES], -1.0, 1.0);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
/* Map the texture */
|
|
mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex);
|
|
mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, 0);
|
|
if (mesh == NULL) {
|
|
glDeleteTextures(1, &texture);
|
|
glXMakeContextCurrent(s->dpy, None, None, NULL);
|
|
glXDestroyPbuffer(s->dpy, pbuffer);
|
|
return;
|
|
}
|
|
|
|
tmu2_gl_map((struct vertex *)mesh,
|
|
s->regs[R_TEXHRES], s->regs[R_TEXVRES],
|
|
s->regs[R_HMESHLAST], s->regs[R_VMESHLAST],
|
|
s->regs[R_DSTHOFFSET], s->regs[R_DSTVOFFSET],
|
|
s->regs[R_DSTSQUAREW], s->regs[R_DSTSQUAREH]);
|
|
cpu_physical_memory_unmap(mesh, mesh_len, 0, mesh_len);
|
|
|
|
/* Write back the OpenGL framebuffer to the QEMU framebuffer */
|
|
fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
|
|
fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 1);
|
|
if (fb == NULL) {
|
|
glDeleteTextures(1, &texture);
|
|
glXMakeContextCurrent(s->dpy, None, None, NULL);
|
|
glXDestroyPbuffer(s->dpy, pbuffer);
|
|
return;
|
|
}
|
|
|
|
glReadPixels(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
|
|
GL_UNSIGNED_SHORT_5_6_5, fb);
|
|
cpu_physical_memory_unmap(fb, fb_len, 1, fb_len);
|
|
|
|
/* Free OpenGL allocs */
|
|
glDeleteTextures(1, &texture);
|
|
glXMakeContextCurrent(s->dpy, None, None, NULL);
|
|
glXDestroyPbuffer(s->dpy, pbuffer);
|
|
|
|
s->regs[R_CTL] &= ~CTL_START_BUSY;
|
|
|
|
trace_milkymist_tmu2_pulse_irq();
|
|
qemu_irq_pulse(s->irq);
|
|
}
|
|
|
|
static uint64_t tmu2_read(void *opaque, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
MilkymistTMU2State *s = opaque;
|
|
uint32_t r = 0;
|
|
|
|
addr >>= 2;
|
|
switch (addr) {
|
|
case R_CTL:
|
|
case R_HMESHLAST:
|
|
case R_VMESHLAST:
|
|
case R_BRIGHTNESS:
|
|
case R_CHROMAKEY:
|
|
case R_VERTICESADDR:
|
|
case R_TEXFBUF:
|
|
case R_TEXHRES:
|
|
case R_TEXVRES:
|
|
case R_TEXHMASK:
|
|
case R_TEXVMASK:
|
|
case R_DSTFBUF:
|
|
case R_DSTHRES:
|
|
case R_DSTVRES:
|
|
case R_DSTHOFFSET:
|
|
case R_DSTVOFFSET:
|
|
case R_DSTSQUAREW:
|
|
case R_DSTSQUAREH:
|
|
case R_ALPHA:
|
|
r = s->regs[addr];
|
|
break;
|
|
|
|
default:
|
|
error_report("milkymist_tmu2: read access to unknown register 0x"
|
|
TARGET_FMT_plx, addr << 2);
|
|
break;
|
|
}
|
|
|
|
trace_milkymist_tmu2_memory_read(addr << 2, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void tmu2_check_registers(MilkymistTMU2State *s)
|
|
{
|
|
if (s->regs[R_BRIGHTNESS] > MAX_BRIGHTNESS) {
|
|
error_report("milkymist_tmu2: max brightness is %d", MAX_BRIGHTNESS);
|
|
}
|
|
|
|
if (s->regs[R_ALPHA] > MAX_ALPHA) {
|
|
error_report("milkymist_tmu2: max alpha is %d", MAX_ALPHA);
|
|
}
|
|
|
|
if (s->regs[R_VERTICESADDR] & 0x07) {
|
|
error_report("milkymist_tmu2: vertex mesh address has to be 64-bit "
|
|
"aligned");
|
|
}
|
|
|
|
if (s->regs[R_TEXFBUF] & 0x01) {
|
|
error_report("milkymist_tmu2: texture buffer address has to be "
|
|
"16-bit aligned");
|
|
}
|
|
}
|
|
|
|
static void tmu2_write(void *opaque, hwaddr addr, uint64_t value,
|
|
unsigned size)
|
|
{
|
|
MilkymistTMU2State *s = opaque;
|
|
|
|
trace_milkymist_tmu2_memory_write(addr, value);
|
|
|
|
addr >>= 2;
|
|
switch (addr) {
|
|
case R_CTL:
|
|
s->regs[addr] = value;
|
|
if (value & CTL_START_BUSY) {
|
|
tmu2_start(s);
|
|
}
|
|
break;
|
|
case R_BRIGHTNESS:
|
|
case R_HMESHLAST:
|
|
case R_VMESHLAST:
|
|
case R_CHROMAKEY:
|
|
case R_VERTICESADDR:
|
|
case R_TEXFBUF:
|
|
case R_TEXHRES:
|
|
case R_TEXVRES:
|
|
case R_TEXHMASK:
|
|
case R_TEXVMASK:
|
|
case R_DSTFBUF:
|
|
case R_DSTHRES:
|
|
case R_DSTVRES:
|
|
case R_DSTHOFFSET:
|
|
case R_DSTVOFFSET:
|
|
case R_DSTSQUAREW:
|
|
case R_DSTSQUAREH:
|
|
case R_ALPHA:
|
|
s->regs[addr] = value;
|
|
break;
|
|
|
|
default:
|
|
error_report("milkymist_tmu2: write access to unknown register 0x"
|
|
TARGET_FMT_plx, addr << 2);
|
|
break;
|
|
}
|
|
|
|
tmu2_check_registers(s);
|
|
}
|
|
|
|
static const MemoryRegionOps tmu2_mmio_ops = {
|
|
.read = tmu2_read,
|
|
.write = tmu2_write,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 4,
|
|
},
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
};
|
|
|
|
static void milkymist_tmu2_reset(DeviceState *d)
|
|
{
|
|
MilkymistTMU2State *s = MILKYMIST_TMU2(d);
|
|
int i;
|
|
|
|
for (i = 0; i < R_MAX; i++) {
|
|
s->regs[i] = 0;
|
|
}
|
|
}
|
|
|
|
static void milkymist_tmu2_init(Object *obj)
|
|
{
|
|
MilkymistTMU2State *s = MILKYMIST_TMU2(obj);
|
|
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
|
|
|
|
sysbus_init_irq(dev, &s->irq);
|
|
|
|
memory_region_init_io(&s->regs_region, obj, &tmu2_mmio_ops, s,
|
|
"milkymist-tmu2", R_MAX * 4);
|
|
sysbus_init_mmio(dev, &s->regs_region);
|
|
}
|
|
|
|
static void milkymist_tmu2_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
MilkymistTMU2State *s = MILKYMIST_TMU2(dev);
|
|
|
|
if (tmu2_glx_init(s)) {
|
|
error_setg(errp, "tmu2_glx_init failed");
|
|
}
|
|
}
|
|
|
|
static const VMStateDescription vmstate_milkymist_tmu2 = {
|
|
.name = "milkymist-tmu2",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(regs, MilkymistTMU2State, R_MAX),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void milkymist_tmu2_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = milkymist_tmu2_realize;
|
|
dc->reset = milkymist_tmu2_reset;
|
|
dc->vmsd = &vmstate_milkymist_tmu2;
|
|
}
|
|
|
|
static const TypeInfo milkymist_tmu2_info = {
|
|
.name = TYPE_MILKYMIST_TMU2,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(MilkymistTMU2State),
|
|
.instance_init = milkymist_tmu2_init,
|
|
.class_init = milkymist_tmu2_class_init,
|
|
};
|
|
|
|
static void milkymist_tmu2_register_types(void)
|
|
{
|
|
type_register_static(&milkymist_tmu2_info);
|
|
}
|
|
|
|
type_init(milkymist_tmu2_register_types)
|