shithub: renderfs

ref: 967ea9815da257e52f38af756ebd93916eaa64cd
dir: /fs.c/

View raw version
#include <u.h>
#include <libc.h>
#include <auth.h>
#include <thread.h>
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
#include <fcall.h>
#include <9p.h>
#include "libobj/obj.h"
#include "libgraphics/graphics.h"

typedef struct Dirtab Dirtab;
typedef struct Client Client;

struct Dirtab
{
	char *name;
	uint perm;
};

struct Client
{
	Ref;
	ulong slot;
	Camera *cam;
};

enum {
	CMviewport,
	CMmove,
	CMprojection,
	CMfov,
	CMclip,
	CMshoot,
};
Cmdtab cmds[] = {
	CMviewport,	"viewport",	3,	/* viewport $width $height */
	CMmove,		"move",		5,	/* move $subject $x $y $z */
	CMprojection,	"projection",	2,	/* projection [persp|ortho] */
	CMfov,		"fov",		2,	/* fov $angle */
	CMclip,		"clip",		3,	/* clip [near|far] $dz */
	CMshoot,	"shoot",	1,	/* shoot */
};

enum {
	Qroot,
	Qnew,
	Qn,
	Qctl,
	Qstats,
	Qframe,
	Qscene,
};

#define QPATH(type, slot)	(((slot)<<8)|(type))
#define QTYPE(path)		((path)&0xFF)
#define SLOT(path)		((path)>>8)

char Ebotch[] = "9P protocol botch";
char Enotfound[] = "file does not exist";
char Enotdir[] = "not a directory";
char Eperm[] = "permission denied";
char Enoscene[] = "no scene";

Dirtab dirtab[] = {
 [Qroot]	"/",		DMDIR|0555,
 [Qnew]		"new",		0666,
 [Qn]		nil,		DMDIR|0555,
 [Qctl]		"ctl",		0666,
 [Qstats]	"stats",	0444,
 [Qframe]	"frame",	0444,
 [Qscene]	"scene",	0666,
};
char *jefe = "Pablo R. Picasso";
Memsubfont *memfont;
Renderer *renderer;
Client *clients;
ulong nclients;
int doprof;

LightSource light = {{0,100,100,1}, {1,1,1,1}, LIGHT_POINT};

static Client *getclient(ulong);

static Point3
vshader(VSparams *sp)
{
	Client *c;
	Point3 pos, lightdir;
	double intens;

	c = getclient(0);	/* TODO figure out a way to address the correct client */

	pos = model2world(sp->su->entity, sp->v->p);
	lightdir = normvec3(subpt3(light.p, pos));
	intens = fmax(0, dotvec3(sp->v->n, lightdir));
	addvattr(sp->v, "intensity", VANumber, &intens);
	if(sp->v->mtl != nil)
		sp->v->c = sp->v->mtl->diffuse;
	return world2clip(c->cam, pos);
}

static Color
fshader(FSparams *sp)
{
	Color tc, c;

	if(sp->v.mtl != nil && sp->v.mtl->diffusemap != nil && sp->v.uv.w != 0)
		tc = texture(sp->v.mtl->diffusemap, sp->v.uv, neartexsampler);
	else if(sp->su->entity->mdl->tex != nil && sp->v.uv.w != 0)
		tc = texture(sp->su->entity->mdl->tex, sp->v.uv, neartexsampler);
	else
		tc = Pt3(1,1,1,1);

	c.a = 1;
	c.b = fclamp(sp->v.c.b*tc.b, 0, 1);
	c.g = fclamp(sp->v.c.g*tc.g, 0, 1);
	c.r = fclamp(sp->v.c.r*tc.r, 0, 1);

	return c;
}

Shadertab auxshaders = {"ident", vshader, fshader};

static int
mode2perm(int m)
{
	static int perms[4] = {4, 2, 6, 1};

	return perms[m&OMASK];
}

static long
strwidth(char *s)
{
	long cw, nc, n;
	Rune r;

	/*
	 * memfont = defont = vga. so we can safely assume all
	 * the glyphs have the same width.
	 */
	nc = 0;
	cw = memfont->info->width;
	while(*s){
		nc += n = chartorune(&r, s);
		s += n;
	}
	return cw*nc;
}

static void
resetcamera(Camera *c)
{
	rmviewport(c->vp);
	c->vp = mkviewport(Rect(0,0,320,200));
	c->fov = 40*DEG;
	c->clip.n = 0.01;
	c->clip.f = 1000;
	c->projtype = PERSPECTIVE;
	c->rctl = renderer;
	placecamera(c, Pt3(0,0,100,1), Pt3(0,0,0,1), Vec3(0,1,0));
	reloadcamera(c);
	memset(&c->stats, 0, sizeof c->stats);
	memset(&c->times, 0, sizeof c->times);
}

static ulong
newclient(void)
{
	Client *c;
	int i;

	for(i = 0; i < nclients; i++)
		if(clients[i].ref == 0)
			return i;

	if(nclients%16 == 0)
		clients = erealloc9p(clients, (nclients+16)*sizeof(*clients));

	c = getclient(nclients++);
	memset(c, 0, sizeof *c);
	c->slot = c-clients;
	c->cam = emalloc9p(sizeof *c->cam);
	resetcamera(c->cam);
	c->cam->s = newscene(nil);
	return c->slot;
}

static Client *
getclient(ulong slot)
{
	if(slot >= nclients)
		return nil;
	return &clients[slot];
}

static void
closeclient(Client *c)
{
	if(decref(c))
		return;

	clearscene(c->cam->s);
	resetcamera(c->cam);
}

static void
fillstat(Dir *d, uvlong path)
{
	Dirtab *t;

	t = &dirtab[QTYPE(path)];
	if(t->name != nil)
		d->name = estrdup9p(t->name);
	else{
		d->name = smprint("%llud", SLOT(path));
		if(d->name == nil)
			sysfatal("smprint: %r");
	}
	d->uid = estrdup9p(jefe);
	d->gid = estrdup9p(jefe);
	d->muid = nil;
	d->atime = d->mtime = time(0);
	d->length = 0;
	d->mode = t->perm;
	d->qid = (Qid){path, 0, t->perm>>24};
}

static int
rootgen(int i, Dir *d, void*)
{
	if(++i >= Qn+nclients)
		return -1;
	fillstat(d, i < Qn? i: QPATH(Qn, i-Qn));
	return 0;
}

static int
clientgen(int i, Dir *d, void *aux)
{
	Client *c;

	c = aux;
	i += Qn+1;
	if(i < nelem(dirtab)){
		fillstat(d, QPATH(i, c->slot));
		return 0;
	}
	return -1;
}

static int
readimg(Memimage *i, char *t, Rectangle r, int offset, int n)
{
	int ww, oo, y, m;
	uchar *tt;

	ww = bytesperline(r, i->depth);
	r.min.y += offset/ww;
	if(r.min.y >= r.max.y)
		return 0;

	y = r.min.y + (n + ww-1)/ww;
	if(y < r.max.y)
		r.max.y = y;

	m = ww * Dy(r);
	oo = offset % ww;
	if(oo == 0 && n >= m)
		return unloadmemimage(i, r, (uchar*)t, n);

	if((tt = malloc(m)) == nil)
		return -1;

	m = unloadmemimage(i, r, tt, m) - oo;
	if(m > 0){
		if(n < m) m = n;
		memmove(t, tt + oo, m);
	}

	free(tt);
	return m;
}

void
fsattach(Req *r)
{
	if(r->ifcall.aname && r->ifcall.aname[0]){
		respond(r, "invalid attach specifier");
		return;
	}

	r->fid->qid = (Qid){Qroot, 0, QTDIR};
	r->ofcall.qid = r->fid->qid;
	respond(r, nil);
}

char *
fswalk1(Fid *f, char *name, Qid *qid)
{
	char buf[32];
	uvlong path;
	ulong n;
	int i;

	path = f->qid.path;
	switch(QTYPE(path)){
	case Qroot:
		if(strcmp(name, "..") == 0){
			*qid = f->qid;
			return nil;
		}
		for(i = 1; i <= Qn; i++){
			if(i == Qn){
				n = strtoul(name, nil, 10);
				snprint(buf, sizeof buf, "%lud", n);
				if(n < nclients && strcmp(buf, name) == 0){
					*qid = (Qid){QPATH(Qn, n), 0, dirtab[Qn].perm>>24};
					f->qid = *qid;
					return nil;
				}
				break;
			}
			if(strcmp(name, dirtab[i].name) == 0){
				*qid = (Qid){QPATH(i, SLOT(path)), 0, dirtab[i].perm>>24};
				f->qid = *qid;
				return nil;
			}
		}
		return Enotfound;
	case Qn:
		if(strcmp(name, "..") == 0){
			*qid = (Qid){Qroot, 0, QTDIR};
			return nil;
		}
		for(i = Qn+1; i < nelem(dirtab); i++)
			if(strcmp(name, dirtab[i].name) == 0){
				*qid = (Qid){QPATH(i, SLOT(path)), 0, dirtab[i].perm>>24};
				f->qid = *qid;
				return nil;
			}
		return Enotfound;
	default:
		return Enotdir;
	}
}

void
fsopen(Req *r)
{
	Dirtab *t;
	uvlong path;
	int perm, want;

	path = r->fid->qid.path;
	if(QTYPE(path) >= nelem(dirtab)){
		respond(r, Ebotch);
		return;
	}
	t = &dirtab[QTYPE(path)];

	perm = t->perm;
	if(strcmp(r->fid->uid, jefe) == 0)
		perm >>= 6;

	if((r->ifcall.mode & (OCEXEC|ORCLOSE)) != 0)
		goto deny;
	want = mode2perm(r->ifcall.mode);
	if((want & perm) != want){
deny:
		respond(r, Eperm);
		return;
	}

	if(QTYPE(path) == Qnew){
		path = QPATH(Qctl, newclient());
		r->fid->qid.path = path;
		r->ofcall.qid.path = path;
	}
	if(QTYPE(path) >= Qn)
		incref(getclient(SLOT(path)));
	respond(r, nil);
}

void
fsread(Req *r)
{
	Client *c;
	Framebuf *fb;
	Memimage *img;
	char buf[1024], cbuf[30], *t;
	uvlong path;
	ulong off, cnt;
	int n;

	path = r->fid->qid.path;
	off = r->ifcall.offset;
	cnt = r->ifcall.count;
	c = getclient(SLOT(path));

	switch(QTYPE(path)){
	default:
		respond(r, "bug in fsread");
		break;
	case Qroot:
		dirread9p(r, rootgen, nil);
		respond(r, nil);
		break;
	case Qn:
		dirread9p(r, clientgen, getclient(SLOT(path)));
		respond(r, nil);
		break;
	case Qctl:
		snprint(buf, sizeof buf, "%llud", SLOT(path));
		readstr(r, buf);
		respond(r, nil);
		break;
	case Qstats:
		n = snprint(buf, sizeof buf, "fps %.0f/%.0f/%.0f/%.0f\n",
			!c->cam->stats.max? 0: 1e9/c->cam->stats.max,
			!c->cam->stats.avg? 0: 1e9/c->cam->stats.avg,
			!c->cam->stats.min? 0: 1e9/c->cam->stats.min,
			!c->cam->stats.v? 0: 1e9/c->cam->stats.v);
		snprint(buf+n, sizeof(buf)-n, "frame #%llud\n", c->cam->stats.nframes);
		readstr(r, buf);
		respond(r, nil);
		break;
	case Qframe:
		fb = c->cam->vp->getfb(c->cam->vp);
		img = fb->cb;
		if(off < 5*12){
			n = snprint(buf, sizeof buf, "%11s %11d %11d %11d %11d ",
				chantostr(cbuf, img->chan),
				img->r.min.x, img->r.min.y, img->r.max.x, img->r.max.y);
			t = estrdup9p(buf);

			if(off > n){
				off = n;
				cnt = 0;
			}

			if(off+cnt > n)
				cnt = n-off;

			r->ofcall.data = t+off;
			r->ofcall.count = cnt;
			respond(r, nil);
			free(t);
			break;
		}

		off -= 5*12;
		n = -1;
		t = malloc(cnt);
		if(t != nil){
			r->ofcall.data = t;
			n = readimg(img, t, img->r, off, cnt);
		}

		if(n < 0){
			buf[0] = 0;
			errstr(buf, sizeof buf);
			respond(r, buf);
		}else{
			r->ofcall.count = n;
			respond(r, nil);
		}
		free(t);
		break;
	case Qscene:
//		snprint(buf, sizeof buf, "%s\n", Enoscene);
		n = snprint(buf, sizeof buf, "viewport %R\n", c->cam->vp->getfb(c->cam->vp)->r);
		n += snprint(buf+n, sizeof(buf)-n, "pos %V\n", c->cam->p);
		n += snprint(buf+n, sizeof(buf)-n, "fov %g°\n", c->cam->fov/DEG);
		n += snprint(buf+n, sizeof(buf)-n, "clip [%g %g]\n", c->cam->clip.n, c->cam->clip.f);
		n += snprint(buf+n, sizeof(buf)-n, "proj %s\n", c->cam->projtype == PERSPECTIVE? "persp": "ortho");
		snprint(buf+n, sizeof(buf)-n, "ents %lud\n", c->cam->s->nents);
		readstr(r, buf);
		respond(r, nil);
		break;
	}
}

void
fswrite(Req *r)
{
	Client *c;
	Model *model;
	OBJ *obj;
	Entity *ent;
	Framebuf *fb;
	Cmdbuf *cb;
	Cmdtab *ct;
	Point pt;
	char *msg, *f[4];
	uvlong path;
	ulong cnt;
	int nf, w, h;
	double clipn, clipf;

	path = r->fid->qid.path;
	cnt = r->ifcall.count;
	c = getclient(SLOT(path));

	switch(QTYPE(path)){
	default:
		respond(r, "bug in fswrite");
		break;
	case Qctl:
		msg = emalloc9p(cnt+1);
		memmove(msg, r->ifcall.data, cnt);
		msg[cnt] = 0;

		cb = parsecmd(msg, strlen(msg));
		ct = lookupcmd(cb, cmds, nelem(cmds));
		if(ct == nil)
			goto nocmd;

		switch(ct->index){
		case CMviewport:
			w = strtoul(cb->f[1], nil, 10);
			h = strtoul(cb->f[2], nil, 10);
			rmviewport(c->cam->vp);
			c->cam->vp = mkviewport(Rect(0,0,w,h));
			break;
		case CMmove:
			if(strcmp(cb->f[1], "camera") == 0){
				c->cam->p.x = strtod(cb->f[2], nil);
				c->cam->p.y = strtod(cb->f[3], nil);
				c->cam->p.z = strtod(cb->f[4], nil);
			}
			break;
		case CMprojection:
			if(strcmp(cb->f[1], "persp") == 0)
				c->cam->projtype = PERSPECTIVE;
			else if(strcmp(cb->f[1], "ortho") == 0)
				c->cam->projtype = ORTHOGRAPHIC;
			reloadcamera(c->cam);
			break;
		case CMfov:
			c->cam->fov = strtod(cb->f[1], nil);

			if(utfrune(cb->f[1], L'°') != nil)
				c->cam->fov *= DEG;

			c->cam->fov = fclamp(c->cam->fov, 1*DEG, 180*DEG);
			reloadcamera(c->cam);
			break;
		case CMclip:
			clipn = c->cam->clip.n;
			clipf = c->cam->clip.f;

			if(strcmp(cb->f[1], "near") == 0)
				clipn = strtod(cb->f[2], nil);
			else if(strcmp(cb->f[1], "far") == 0)
				clipf = strtod(cb->f[2], nil);

			if(clipn >= clipf)
				break;

			c->cam->clip.n = clipn;
			c->cam->clip.f = clipf;
			reloadcamera(c->cam);
			break;
		case CMshoot:
			if(c->cam->s->nents < 1){
				fb = c->cam->vp->getfb(c->cam->vp);
				pt = Pt(Dx(fb->r)/2-strwidth(Enoscene)/2,Dy(fb->r)/2-memfont->height/2);
				memimagedraw(fb->cb, fb->r, memwhite, ZP, nil, ZP, S);
				memimagestring(fb->cb, pt, memblack, ZP, memfont, Enoscene);
				break;
			}
			shootcamera(c->cam, &auxshaders);
			break;
		}
nocmd:
		free(cb);
		free(msg);
		r->ofcall.count = cnt;
		respond(r, nil);
		break;
	case Qscene:
		msg = emalloc9p(cnt+1);
		memmove(msg, r->ifcall.data, cnt);
		msg[cnt] = 0;

		nf = tokenize(msg, f, nelem(f));
		if(nf != 1)
			goto noscene;

		fprint(2, "loading obj from %s\n", msg);

		/* TODO load an actual scene (format tbd) */
		model = newmodel();
		if((obj = objparse(f[0])) == nil){
			delmodel(model);
			goto noscene;
		}
		loadobjmodel(model, obj);
		objfree(obj);
		ent = newentity(model);
		c->cam->s->addent(c->cam->s, ent);
noscene:
		free(msg);
		r->ofcall.count = cnt;
		respond(r, nil);
		break;
	}
}

void
fsstat(Req *r)
{
	fillstat(&r->d, r->fid->qid.path);
	respond(r, nil);	
}

void
fsdestroyfid(Fid *f)
{
	uvlong path;

	path = f->qid.path;
	if(f->omode != -1 && QTYPE(path) >= Qn)
		closeclient(getclient(SLOT(path)));
}

void
fsending(Srv*)
{
	threadexitsall(nil);
}

Srv fs = {
	.attach		= fsattach,
	.walk1		= fswalk1,
	.open		= fsopen,
	.read		= fsread,
	.write		= fswrite,
	.stat		= fsstat,
	.destroyfid	= fsdestroyfid,
	.end		= fsending,
};

static void
confproc(void)
{
	char buf[64];
	int fd;

	snprint(buf, sizeof buf, "/proc/%d/ctl", getpid());
	fd = open(buf, OWRITE);
	if(fd < 0)
		sysfatal("open: %r");

	if(doprof)
		fprint(fd, "profile\n");

	close(fd);
}

void
usage(void)
{
	fprint(2, "usage: %s [-Dp] [-s srvname] [-m mtpt]\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char *argv[])
{
	char *srvname, *mtpt;

	srvname = "render";
	mtpt = "/mnt/render";
	GEOMfmtinstall();
	ARGBEGIN{
	case 'D':
		chatty9p++;
		break;
	case 'p':
		doprof++;
		break;
	case 's':
		srvname = EARGF(usage());
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	default: usage();
	}ARGEND
	if(argc != 0)
		usage();

	confproc();
	jefe = getuser();

	if(memimageinit() != 0)
		sysfatal("memimageinit: %r");
	if((renderer = initgraphics()) == nil)
		sysfatal("initgraphics: %r");
	memfont = getmemdefont();

	threadpostmountsrv(&fs, srvname, mtpt, MREPL|MCREATE);
	threadexits(nil);
}