ref: 8551f75ceaafd6678d8e489abfa82f5b414ef42a
dir: /fs.c/
#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;
static 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.r = sp->v->mtl->Kd.r;
sp->v->c.g = sp->v->mtl->Kd.g;
sp->v->c.b = sp->v->mtl->Kd.b;
sp->v->c.a = 1;
}
return world2clip(c->cam, pos);
}
static Color
fshader(FSparams *sp)
{
Color tc, c;
if(sp->v.mtl != nil && sp->v.mtl->map_Kd != nil && sp->v.uv.w != 0)
tc = texture(sp->v.mtl->map_Kd, 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 = fclamp(sp->v.c.a*tc.a, 0, 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);
}
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 *i;
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);
i = fb->cb;
if(off < 5*12){
n = snprint(buf, sizeof buf, "%11s %11d %11d %11d %11d ",
chantostr(cbuf, i->chan),
i->r.min.x, i->r.min.y, i->r.max.x, i->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(i, t, i->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;
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((model->obj = objparse(f[0])) == nil){
delmodel(model);
goto noscene;
}
refreshmodel(model);
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,
};
void
usage(void)
{
fprint(2, "usage: %s [-D] [-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 's':
srvname = EARGF(usage());
break;
case 'm':
mtpt = EARGF(usage());
break;
default: usage();
}ARGEND
if(argc != 0)
usage();
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);
}