shithub: libgraphics

Download patch

ref: 37da5f0a27740bb43bac67a2eb98f725beca975e
parent: df792cbb5cf94abf0d286ebfd73bad9714dce509
author: rodri <rgl@antares-labs.eu>
date: Tue Jan 30 07:01:01 EST 2024

import the new renderer and clean things up.

i integrated the renderer i've been developing
on the tinyrend repo and got rid of a bunch of
stuff that's no longer necessary. also began
structuring things to fit the new interface i
have in mind.

there are still some artifacts with the
projection xforms that cause issues with
clipping and division by zero.

--- /dev/null
+++ b/alloc.c
@@ -1,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == nil)
+		sysfatal("malloc: %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+	void *np;
+
+	np = realloc(p, n);
+	if(np == nil){
+		if(n == 0)
+			return nil;
+		sysfatal("realloc: %r");
+	}
+	if(p == nil)
+		setmalloctag(np, getcallerpc(&p));
+	else
+		setrealloctag(np, getcallerpc(&p));
+	return np;
+}
+
+Memimage *
+eallocmemimage(Rectangle r, ulong chan)
+{
+	Memimage *i;
+
+	i = allocmemimage(r, chan);
+	if(i == nil)
+		sysfatal("allocmemimage: %r");
+	memfillcolor(i, DTransparent);
+	return i;
+}
--- a/camera.c
+++ b/camera.c
@@ -1,32 +1,41 @@
 #include <u.h>
 #include <libc.h>
+#include <thread.h>
 #include <draw.h>
+#include <memdraw.h>
 #include <geometry.h>
-#include <graphics.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
 
-static int
-max(int a, int b)
+static void
+updatestats(Camera *c, uvlong v)
 {
-	return a > b ? a : b;
+	c->stats.v = v;
+	c->stats.n++;
+	c->stats.acc += v;
+	c->stats.avg = c->stats.acc/c->stats.n;
+	c->stats.min = v < c->stats.min || c->stats.n == 1? v: c->stats.min;
+	c->stats.max = v > c->stats.max || c->stats.n == 1? v: c->stats.max;
 }
 
 static void
 verifycfg(Camera *c)
 {
-	assert(c->viewport != nil);
-	if(c->ptype == Ppersp)
+	assert(c->vp != nil);
+	if(c->projtype == PERSPECTIVE)
 		assert(c->fov > 0 && c->fov < 360*DEG);
 	assert(c->clip.n > 0 && c->clip.n < c->clip.f);
 }
 
 void
-configcamera(Camera *c, Image *v, double fov, double n, double f, Projection p)
+configcamera(Camera *c, Viewport *v, double fov, double n, double f, Projection p)
 {
-	c->viewport = v;
+	c->vp = v;
 	c->fov = fov;
 	c->clip.n = n;
 	c->clip.f = f;
-	c->ptype = p;
+	c->projtype = p;
 	reloadcamera(c);
 }
 
@@ -55,23 +64,37 @@
 	double l, r, b, t;
 
 	verifycfg(c);
-	switch(c->ptype){
-	case Portho:
+	switch(c->projtype){
+	case ORTHOGRAPHIC:
 		/*
-		r = Dx(c->viewport->r)/2;
-		t = Dy(c->viewport->r)/2;
+		r = Dx(c->vp->fbctl->fb[0]->r)/2;
+		t = Dy(c->vp->fbctl->fb[0]->r)/2;
 		l = -r;
 		b = -t;
 		*/
 		l = t = 0;
-		r = Dx(c->viewport->r);
-		b = Dy(c->viewport->r);
+		r = Dx(c->vp->fbctl->fb[0]->r);
+		b = Dy(c->vp->fbctl->fb[0]->r);
 		orthographic(c->proj, l, r, b, t, c->clip.n, c->clip.f);
 		break;
-	case Ppersp:
-		a = (double)Dx(c->viewport->r)/Dy(c->viewport->r);
+	case PERSPECTIVE:
+		a = (double)Dx(c->vp->fbctl->fb[0]->r)/Dy(c->vp->fbctl->fb[0]->r);
 		perspective(c->proj, c->fov, a, c->clip.n, c->clip.f);
 		break;
 	default: sysfatal("unknown projection type");
 	}
+}
+
+void
+shootcamera(Camera *c, OBJ *m, Memimage *tex, Shader *s)
+{
+	uvlong t0, t1;
+
+	c->vp->fbctl->reset(c->vp->fbctl);
+	t0 = nanosec();
+	shade(c->vp->fbctl->fb[c->vp->fbctl->idx^1], m, tex, s, 1);	/* address the back buffer */
+	t1 = nanosec();
+	c->vp->fbctl->swap(c->vp->fbctl);
+
+	updatestats(c, t1-t0);
 }
--- /dev/null
+++ b/fb.c
@@ -1,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
+
+static void
+framebufctl_draw(Framebufctl *ctl, Memimage *dst)
+{
+	lock(&ctl->swplk);
+	memimagedraw(dst, dst->r, ctl->fb[ctl->idx]->cb, ZP, nil, ZP, SoverD);
+	unlock(&ctl->swplk);
+}
+
+static void
+framebufctl_swap(Framebufctl *ctl)
+{
+	lock(&ctl->swplk);
+	ctl->idx ^= 1;
+	unlock(&ctl->swplk);
+}
+
+static void
+framebufctl_reset(Framebufctl *ctl)
+{
+	Framebuf *fb;
+
+	/* address the back buffer—resetting the front buffer is VERBOTEN */
+	fb = ctl->fb[ctl->idx^1];
+	memsetd(fb->zbuf, Inf(-1), Dx(fb->r)*Dy(fb->r));
+	memfillcolor(fb->cb, DTransparent);
+}
+
+Framebuf *
+mkfb(Rectangle r)
+{
+	Framebuf *fb;
+
+	fb = emalloc(sizeof *fb);
+	memset(fb, 0, sizeof *fb);
+	fb->cb = eallocmemimage(r, RGBA32);
+	fb->zbuf = emalloc(Dx(r)*Dy(r)*sizeof(*fb->zbuf));
+	memsetd(fb->zbuf, Inf(-1), Dx(r)*Dy(r));
+	fb->r = r;
+	return fb;
+}
+
+void
+rmfb(Framebuf *fb)
+{
+	free(fb->zbuf);
+	freememimage(fb->cb);
+	free(fb);
+}
+
+Framebufctl *
+mkfbctl(Rectangle r)
+{
+	Framebufctl *fc;
+
+	fc = emalloc(sizeof *fc);
+	memset(fc, 0, sizeof *fc);
+	fc->fb[0] = mkfb(r);
+	fc->fb[1] = mkfb(r);
+	fc->draw = framebufctl_draw;
+	fc->swap = framebufctl_swap;
+	fc->reset = framebufctl_reset;
+	return fc;
+}
+
+void
+rmfbctl(Framebufctl *fc)
+{
+	rmfb(fc->fb[1]);
+	rmfb(fc->fb[0]);
+	free(fc);
+}
--- a/graphics.h
+++ b/graphics.h
@@ -1,18 +1,24 @@
+#define HZ2MS(hz)	(1000/(hz))
+
 typedef enum {
-	Portho,		/* orthographic */
-	Ppersp		/* perspective */
+	ORTHOGRAPHIC,
+	PERSPECTIVE
 } Projection;
 
 typedef struct Color Color;
 typedef struct Vertex Vertex;
-typedef struct Framebuffer Framebuffer;
+typedef struct VSparams VSparams;
+typedef struct FSparams FSparams;
+typedef struct SUparams SUparams;
+typedef struct Shader Shader;
+typedef struct Framebuf Framebuf;
+typedef struct Framebufctl Framebufctl;
 typedef struct Viewport Viewport;
 typedef struct Camera Camera;
-typedef struct Triangle Triangle;
 
 struct Color
 {
-	double r, g, b;
+	double r, g, b, a;
 };
 
 struct Vertex
@@ -23,61 +29,122 @@
 	Point2 uv;	/* texture coordinate */
 };
 
-struct Framebuffer
+typedef Vertex Triangle[3];
+
+/* shader params */
+struct VSparams
 {
-	Rectangle r;	/* frame geometry */
-	int bpp;	/* bytes per pixel */
-	uchar *color;	/* pixel color buffer */
-	float *depth;	/* pixel depth buffer */
+	SUparams *su;
+	Point3 *p;
+	Point3 *n;
+	uint idx;
 };
 
+struct FSparams
+{
+	SUparams *su;
+	Memimage *frag;
+	Point p;
+	Point3 bc;
+	uchar *cbuf;
+};
+
+/* shader unit params */
+struct SUparams
+{
+	Framebuf *fb;
+	OBJElem **b, **e;
+	int id;
+	Channel *donec;
+
+	/* TODO replace with a Scene */
+	OBJ *model;
+	Memimage *modeltex;
+
+	double var_intensity[3];
+
+	uvlong uni_time;
+
+	Point3 (*vshader)(VSparams*);
+	Memimage *(*fshader)(FSparams*);
+};
+
+struct Shader
+{
+	char *name;
+	Point3 (*vshader)(VSparams*);		/* vertex shader */
+	Memimage *(*fshader)(FSparams*);	/* fragment shader */
+};
+
+struct Framebuf
+{
+	Memimage *cb;	/* color buffer */
+	double *zbuf;	/* z/depth buffer */
+	Lock zbuflk;
+	Rectangle r;
+};
+
+struct Framebufctl
+{
+	Framebuf *fb[2];	/* double buffering */
+	uint idx;		/* front buffer index */
+	Lock swplk;
+
+	void (*draw)(Framebufctl*, Memimage*);
+	void (*swap)(Framebufctl*);
+	void (*reset)(Framebufctl*);
+};
+
 struct Viewport
 {
 	RFrame;
-	Framebuffer;
+	Framebufctl *fbctl;
 };
 
 struct Camera
 {
 	RFrame3;		/* VCS */
-	Image *viewport;
+	Viewport *vp;
 	double fov;		/* vertical FOV */
 	struct {
 		double n, f;	/* near and far clipping planes */
 	} clip;
 	Matrix3 proj;		/* VCS to NDC xform */
-	Projection ptype;
-};
+	Projection projtype;
 
-struct Triangle
-{
-	Point p0, p1, p2;
+	struct {
+		uvlong min, avg, max, acc, n, v;
+	} stats;
 };
 
-/* Camera */
-void configcamera(Camera*, Image*, double, double, double, Projection);
+/* camera */
+void configcamera(Camera*, Viewport*, double, double, double, Projection);
 void placecamera(Camera*, Point3, Point3, Point3);
 void aimcamera(Camera*, Point3);
 void reloadcamera(Camera*);
+void shootcamera(Camera*, OBJ*, Memimage*, Shader*);
 
-/* rendering */
-#define FPS	(60)		/* frame rate */
-#define MS2FR	(1e3/FPS)	/* ms per frame */
+/* viewport */
+Viewport *mkviewport(Rectangle);
+void rmviewport(Viewport*);
+
+/* render */
 Point3 world2vcs(Camera*, Point3);
 Point3 vcs2ndc(Camera*, Point3);
 Point3 world2ndc(Camera*, Point3);
-int isclipping(Point3);
-int clipline3(Point3*, Point3*);
-Point toviewport(Camera*, Point3);
-Point2 fromviewport(Camera*, Point);
+Point3 ndc2viewport(Camera*, Point3);
 void perspective(Matrix3, double, double, double, double);
 void orthographic(Matrix3, double, double, double, double, double, double);
-/* temporary debug helpers */
-void line3(Camera*, Point3, Point3, int, int, Image*);
-Point string3(Camera*, Point3, Image*, Font*, char*);
 
-/* triangle */
-Triangle Trian(int, int, int, int, int, int);
-Triangle Trianpt(Point, Point, Point);
-void triangle(Image*, Triangle, int, Image*, Point);
-void filltriangle(Image*, Triangle, Image*, Point);
+/* util */
+double fmin(double, double);
+double fmax(double, double);
+Memimage *rgb(ulong);
+Memimage *readtga(char*);
+Memimage *readpng(char*);
+
+/* shadeop */
+double step(double, double);
+double smoothstep(double, double, double);
+
+extern Rectangle UR;	/* unit rectangle */
--- /dev/null
+++ b/internal.h
@@ -1,0 +1,29 @@
+typedef struct Deco Deco;
+struct Deco
+{
+	int pfd[2];
+	int infd;
+	char *prog;
+};
+
+/* alloc */
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+Memimage *eallocmemimage(Rectangle, ulong);
+
+/* fb */
+Framebuf *mkfb(Rectangle);
+void rmfb(Framebuf*);
+Framebufctl *mkfbctl(Rectangle);
+void rmfbctl(Framebufctl*);
+
+/* render */
+void shade(Framebuf *fb, OBJ *model, Memimage *modeltex, Shader *s, ulong nprocs);
+
+/* util */
+int min(int, int);
+int max(int, int);
+void memsetd(double*, double, usize);
+
+/* nanosec */
+uvlong nanosec(void);
--- a/mkfile
+++ b/mkfile
@@ -3,9 +3,35 @@
 LIB=libgraphics.a$O
 OFILES=\
 	camera.$O\
+	viewport.$O\
 	render.$O\
-	triangle.$O\
+	alloc.$O\
+	fb.$O\
+	shadeop.$O\
+	util.$O\
+	nanosec.$O\
 
-HFILES=graphics.h
+HFILES=\
+	graphics.h\
+	internal.h\
+	libobj/obj.h
 
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
 </sys/src/cmd/mklib
+
+libobj/libobj.a$O:
+	cd libobj
+	mk install
+
+pulldeps:VQ:
+	git/clone git://antares-labs.eu/libobj || \
+	git/clone git://shithub.us/rodri/libobj || \
+	git/clone https://github.com/sametsisartenep/libobj
+
+clean nuke:V:
+	rm -f *.[$OS] $LIB
+	@{cd libobj; mk $target}
--- /dev/null
+++ b/nanosec.c
@@ -1,0 +1,109 @@
+#include <u.h>
+#include <libc.h>
+#include <tos.h>
+
+/*
+ * This code is a mixture of cpuid(1) and the nanosec() found in vmx,
+ * in order to force the use of nsec(2) in case we are running in a
+ * virtualized environment where the clock is mis-bhyve-ing.
+ */
+
+typedef struct Res {
+	ulong ax, bx, cx, dx;
+} Res;
+
+static uchar _cpuid[] = {
+	0x5E,			/* POP SI (PC) */
+	0x5D,			/* POP BP (Res&) */
+	0x58,			/* POP AX */
+	0x59,			/* POP CX */
+
+	0x51,			/* PUSH CX */
+	0x50,			/* PUSH AX */
+	0x55,			/* PUSH BP */
+	0x56,			/* PUSH SI */
+
+	0x31, 0xDB,		/* XOR BX, BX */
+	0x31, 0xD2,		/* XOR DX, DX */
+
+	0x0F, 0xA2,		/* CPUID */
+
+	0x89, 0x45, 0x00,	/* MOV AX, 0(BP) */
+	0x89, 0x5d, 0x04,	/* MOV BX, 4(BP) */
+	0x89, 0x4d, 0x08,	/* MOV CX, 8(BP) */
+	0x89, 0x55, 0x0C,	/* MOV DX, 12(BP) */
+	0xC3,			/* RET */
+};
+
+static Res (*cpuid)(ulong ax, ulong cx) = (Res(*)(ulong, ulong)) _cpuid;
+
+/*
+ * nsec() is wallclock and can be adjusted by timesync
+ * so need to use cycles() instead, but fall back to
+ * nsec() in case we can't
+ */
+uvlong
+nanosec(void)
+{
+	static uvlong fasthz, xstart;
+	char buf[13], path[128];
+	ulong w;
+	uvlong x, div;
+	int fd;
+	Res r;
+
+	if(fasthz == ~0ULL)
+		return nsec() - xstart;
+
+	if(fasthz == 0){
+		/* first long in a.out header */
+		snprint(path, sizeof path, "/proc/%d/text", getpid());
+		fd = open(path, OREAD);
+		if(fd < 0)
+			goto Wallclock;
+		if(read(fd, buf, 4) != 4){
+			close(fd);
+			goto Wallclock;
+		}
+		close(fd);
+
+		w = ((ulong *) buf)[0];
+
+		switch(w){
+		default:
+			goto Wallclock;
+		case 0x978a0000:	/* amd64 */
+			/* patch out POP BP -> POP AX */
+			_cpuid[1] = 0x58;
+		case 0xeb010000:	/* 386 */
+			break;
+		}
+		segflush(_cpuid, sizeof(_cpuid));
+
+		r = cpuid(0x40000000, 0);
+		((ulong *) buf)[0] = r.bx;
+		((ulong *) buf)[1] = r.cx;
+		((ulong *) buf)[2] = r.dx;
+		buf[12] = 0;
+
+		if(strstr(buf, "bhyve") != nil)
+			goto Wallclock;
+
+		if(_tos->cyclefreq){
+			fasthz = _tos->cyclefreq;
+			cycles(&xstart);
+		} else {
+Wallclock:
+			fasthz = ~0ULL;
+			xstart = nsec();
+		}
+		return 0;
+	}
+	cycles(&x);
+	x -= xstart;
+
+	/* this is ugly */
+	for(div = 1000000000ULL; x < 0x1999999999999999ULL && div > 1 ; div /= 10ULL, x *= 10ULL);
+
+	return x / (fasthz / div);
+}
--- a/readme
+++ b/readme
@@ -1,6 +1,6 @@
 libgraphics
 
-Libgraphics provides 3D computer graphics through draw(3).
+Libgraphics provides 3D computer graphics through memdraw(2).
 
 Still in early stages of research, subject to change, drastically, at
 any given time.
--- a/render.c
+++ b/render.c
@@ -1,31 +1,85 @@
 #include <u.h>
 #include <libc.h>
+#include <thread.h>
 #include <draw.h>
+#include <memdraw.h>
 #include <geometry.h>
-#include <graphics.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
 
-static Point2
-flatten(Camera *c, Point3 p)
+Rectangle UR = {0,0,1,1};
+
+
+static void
+pixel(Memimage *dst, Point p, Memimage *src)
 {
-	Point2 p2;
-	Matrix S = {
-		Dx(c->viewport->r)/2, 0, 0,
-		0, Dy(c->viewport->r)/2, 0,
-		0, 0, 1,
-	}, T = {
-		1, 0, 1,
-		0, 1, 1,
-		0, 0, 1,
-	};
+	if(dst == nil || src == nil)
+		return;
 
-	p2 = Pt2(p.x, p.y, p.w);
-	if(p2.w != 0)
-		p2 = divpt2(p2, p2.w);
-	mulm(S, T);
-	p2 = xform(p2, S);
-	return p2;
+	memimagedraw(dst, rectaddpt(UR, p), src, ZP, nil, ZP, SoverD);
 }
 
+/*
+ * it only processes quads for now.
+ */
+static int
+triangulate(OBJElem **newe, OBJElem *e)
+{
+	OBJIndexArray *newidxtab;
+	OBJIndexArray *idxtab;
+
+	idxtab = &e->indextab[OBJVGeometric];
+	newe[0] = emalloc(sizeof *newe[0]);
+	newe[0]->type = OBJEFace;
+	newidxtab = &newe[0]->indextab[OBJVGeometric];
+	newidxtab->nindex = 3;
+	newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+	newidxtab->indices[0] = idxtab->indices[0];
+	newidxtab->indices[1] = idxtab->indices[1];
+	newidxtab->indices[2] = idxtab->indices[2];
+	idxtab = &e->indextab[OBJVTexture];
+	newidxtab = &newe[0]->indextab[OBJVTexture];
+	newidxtab->nindex = 3;
+	newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+	newidxtab->indices[0] = idxtab->indices[0];
+	newidxtab->indices[1] = idxtab->indices[1];
+	newidxtab->indices[2] = idxtab->indices[2];
+	idxtab = &e->indextab[OBJVNormal];
+	newidxtab = &newe[0]->indextab[OBJVNormal];
+	newidxtab->nindex = 3;
+	newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+	newidxtab->indices[0] = idxtab->indices[0];
+	newidxtab->indices[1] = idxtab->indices[1];
+	newidxtab->indices[2] = idxtab->indices[2];
+
+	idxtab = &e->indextab[OBJVGeometric];
+	newe[1] = emalloc(sizeof *newe[1]);
+	newe[1]->type = OBJEFace;
+	newidxtab = &newe[1]->indextab[OBJVGeometric];
+	newidxtab->nindex = 3;
+	newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+	newidxtab->indices[0] = idxtab->indices[0];
+	newidxtab->indices[1] = idxtab->indices[2];
+	newidxtab->indices[2] = idxtab->indices[3];
+	idxtab = &e->indextab[OBJVTexture];
+	newidxtab = &newe[1]->indextab[OBJVTexture];
+	newidxtab->nindex = 3;
+	newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+	newidxtab->indices[0] = idxtab->indices[0];
+	newidxtab->indices[1] = idxtab->indices[2];
+	newidxtab->indices[2] = idxtab->indices[3];
+	idxtab = &e->indextab[OBJVNormal];
+	newidxtab = &newe[1]->indextab[OBJVNormal];
+	newidxtab->nindex = 3;
+	newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+	newidxtab->indices[0] = idxtab->indices[0];
+	newidxtab->indices[1] = idxtab->indices[2];
+	newidxtab->indices[2] = idxtab->indices[3];
+
+	return 2;
+}
+
 Point3
 world2vcs(Camera *c, Point3 p)
 {
@@ -44,105 +98,22 @@
 	return vcs2ndc(c, world2vcs(c, p));
 }
 
-/* requires p to be in NDC */
-int
-isclipping(Point3 p)
+Point3
+ndc2viewport(Camera *c, Point3 p)
 {
-	if(p.x > p.w || p.x < -p.w ||
-	   p.y > p.w || p.y < -p.w ||
-	   p.z > p.w || p.z < 0)
-		return 1;
-	return 0;
-}
+	Matrix3 view;
 
-/* Liang-Barsky algorithm, requires p0, p1 to be in NDC */
-int
-clipline3(Point3 *p0, Point3 *p1)
-{
-	Point3 q0, q1, v;
-	int m0, m1, i;
-	double ti, to, th;
-	double c0[3*2] = {
-		p0->w + p0->x, p0->w - p0->x, p0->w + p0->y,
-		p0->w - p0->y,         p0->z, p0->w - p0->z,
-	}, c1[3*2] = {
-		p1->w + p1->x, p1->w - p1->x, p1->w + p1->y,
-		p1->w - p1->y,         p1->z, p1->w - p1->z,
-	};
+	identity3(view);
+	view[0][3] = c->vp->fbctl->fb[0]->r.max.x/2.0;
+	view[1][3] = c->vp->fbctl->fb[0]->r.max.y/2.0;
+	view[2][3] = 1.0/2.0;
+	view[0][0] = Dx(c->vp->fbctl->fb[0]->r)/2.0;
+	view[1][1] = -Dy(c->vp->fbctl->fb[0]->r)/2.0;
+	view[2][2] = 1.0/2.0;
 
-	/* bit-encoded regions */
-	m0 = (c0[0] < 0) << 0 |
-	     (c0[1] < 0) << 1 |
-	     (c0[2] < 0) << 2 |
-	     (c0[3] < 0) << 3 |
-	     (c0[4] < 0) << 4 |
-	     (c0[5] < 0) << 5;
-	m1 = (c1[0] < 0) << 0 |
-	     (c1[1] < 0) << 1 |
-	     (c1[2] < 0) << 2 |
-	     (c1[3] < 0) << 3 |
-	     (c1[4] < 0) << 4 |
-	     (c1[5] < 0) << 5;
-
-	if((m0 & m1) != 0)
-		return 1;	/* trivially rejected */
-	if((m0 | m1) == 0)
-		return 0;	/* trivially accepted */
-
-	ti = 0;
-	to = 1;
-	for(i = 0; i < 3*2; i++){
-		if(c1[i] < 0){
-			th = c0[i] / (c0[i]-c1[i]);
-			if(th < to)
-				to = th;
-		}else if(c0[i] < 0){
-			th = c0[i] / (c0[i]-c1[i]);
-			if(th < ti)
-				ti = th;
-		}
-		if(ti > to)
-			return 1;
-	}
-
-	/* chop line to fit inside NDC */
-	q0 = *p0;
-	q1 = *p1;
-	v = subpt3(q1, q0);
-	if(m0 != 0)
-		*p0 = addpt3(q0, mulpt3(v, ti));
-	if(m1 != 0)
-		*p1 = addpt3(q0, mulpt3(v, to));
-
-	return 0;
+	return xform3(p, view);
 }
 
-Point
-toviewport(Camera *c, Point3 p)
-{
-	Point2 p2;
-	RFrame rf = {
-		c->viewport->r.min.x, c->viewport->r.max.y, 1,
-		1, 0, 0,
-		0, -1, 0
-	};
-
-	p2 = invrframexform(flatten(c, p), rf);
-	return Pt(p2.x, p2.y);
-}
-
-Point2
-fromviewport(Camera *c, Point p)
-{
-	RFrame rf = {
-		c->viewport->r.min.x, c->viewport->r.max.y, 1,
-		1, 0, 0,
-		0, -1, 0
-	};
-
-	return rframexform(Pt2(p.x,p.y,1), rf);
-}
-
 void
 perspective(Matrix3 m, double fov, double a, double n, double f)
 {
@@ -152,7 +123,7 @@
 	identity3(m);
 	m[0][0] =  cotan/a;
 	m[1][1] =  cotan;
-	m[2][2] = -(f+n)/(f-n);
+	m[2][2] = (f+n)/(f-n);
 	m[2][3] = -2*f*n/(f-n);
 	m[3][2] = -1;
 }
@@ -169,21 +140,213 @@
 	m[2][3] = -(f + n)/(f - n);
 }
 
-void
-line3(Camera *c, Point3 p0, Point3 p1, int end0, int end1, Image *src)
+static void
+rasterize(SUparams *params, Triangle t, Memimage *frag)
 {
-	p0 = world2ndc(c, p0);
-	p1 = world2ndc(c, p1);
-	if(clipline3(&p0, &p1))
-		return;
-	line(c->viewport, toviewport(c, p0), toviewport(c, p1), end0, end1, 0, src, ZP);
+	FSparams fsp;
+	Triangle2 t₂, tt₂;
+	Rectangle bbox;
+	Point p, tp;
+	Point3 bc;
+	double z, w, depth;
+	uchar cbuf[4];
+
+	t₂.p0 = Pt2(t[0].p.x/t[0].p.w, t[0].p.y/t[0].p.w, 1);
+	t₂.p1 = Pt2(t[1].p.x/t[1].p.w, t[1].p.y/t[1].p.w, 1);
+	t₂.p2 = Pt2(t[2].p.x/t[2].p.w, t[2].p.y/t[2].p.w, 1);
+	/* find the triangle's bbox and clip it against the fb */
+	bbox = Rect(
+		min(min(t₂.p0.x, t₂.p1.x), t₂.p2.x), min(min(t₂.p0.y, t₂.p1.y), t₂.p2.y),
+		max(max(t₂.p0.x, t₂.p1.x), t₂.p2.x)+1, max(max(t₂.p0.y, t₂.p1.y), t₂.p2.y)+1
+	);
+	bbox.min.x = max(bbox.min.x, params->fb->r.min.x);
+	bbox.min.y = max(bbox.min.y, params->fb->r.min.y);
+	bbox.max.x = min(bbox.max.x, params->fb->r.max.x);
+	bbox.max.y = min(bbox.max.y, params->fb->r.max.y);
+	cbuf[0] = 0xFF;
+	fsp.su = params;
+	fsp.frag = frag;
+	fsp.cbuf = cbuf;
+
+	for(p.y = bbox.min.y; p.y < bbox.max.y; p.y++)
+		for(p.x = bbox.min.x; p.x < bbox.max.x; p.x++){
+			bc = barycoords(t₂, Pt2(p.x,p.y,1));
+			if(bc.x < 0 || bc.y < 0 || bc.z < 0)
+				continue;
+
+			z = t[0].p.z*bc.x + t[1].p.z*bc.y + t[2].p.z*bc.z;
+			w = t[0].p.w*bc.x + t[1].p.w*bc.y + t[2].p.w*bc.z;
+			depth = fclamp(z/w, 0, 1);
+			lock(&params->fb->zbuflk);
+			if(depth <= params->fb->zbuf[p.x + p.y*Dx(params->fb->r)]){
+				unlock(&params->fb->zbuflk);
+				continue;
+			}
+			params->fb->zbuf[p.x + p.y*Dx(params->fb->r)] = depth;
+			unlock(&params->fb->zbuflk);
+
+			cbuf[0] = 0xFF;
+			if((t[0].uv.w + t[1].uv.w + t[2].uv.w) != 0){
+				tt₂.p0 = mulpt2(t[0].uv, bc.x);
+				tt₂.p1 = mulpt2(t[1].uv, bc.y);
+				tt₂.p2 = mulpt2(t[2].uv, bc.z);
+
+				tp.x = (tt₂.p0.x + tt₂.p1.x + tt₂.p2.x)*Dx(params->modeltex->r);
+				tp.y = (1 - (tt₂.p0.y + tt₂.p1.y + tt₂.p2.y))*Dy(params->modeltex->r);
+
+				switch(params->modeltex->chan){
+				case RGB24:
+					unloadmemimage(params->modeltex, rectaddpt(UR, tp), cbuf+1, sizeof cbuf - 1);
+					break;
+				case RGBA32:
+					unloadmemimage(params->modeltex, rectaddpt(UR, tp), cbuf, sizeof cbuf);
+					break;
+				}
+			}else
+				memset(cbuf+1, 0xFF, sizeof cbuf - 1);
+
+			fsp.p = p;
+			fsp.bc = bc;
+			pixel(params->fb->cb, p, params->fshader(&fsp));
+		}
 }
 
-Point
-string3(Camera *c, Point3 p, Image *src, Font *f, char *s)
+static void
+shaderunit(void *arg)
 {
-	p = world2ndc(c, p);
-	if(isclipping(p))
-		return Pt(-1,-1);
-	return string(c->viewport, toviewport(c, p), src, ZP, f, s);
+	SUparams *params;
+	VSparams vsp;
+	Memimage *frag;
+	OBJVertex *verts, *tverts, *nverts;	/* geometric, texture and normals vertices */
+	OBJIndexArray *idxtab;
+	OBJElem **ep;
+	Triangle t;
+	Point3 n;				/* surface normal */
+
+	params = arg;
+	vsp.su = params;
+	frag = rgb(DBlack);
+
+	threadsetname("shader unit #%d", params->id);
+
+	verts = params->model->vertdata[OBJVGeometric].verts;
+	tverts = params->model->vertdata[OBJVTexture].verts;
+	nverts = params->model->vertdata[OBJVNormal].verts;
+
+	for(ep = params->b; ep != params->e; ep++){
+		idxtab = &(*ep)->indextab[OBJVGeometric];
+
+		t[0].p = Pt3(verts[idxtab->indices[0]].x,verts[idxtab->indices[0]].y,verts[idxtab->indices[0]].z,verts[idxtab->indices[0]].w);
+		t[1].p = Pt3(verts[idxtab->indices[1]].x,verts[idxtab->indices[1]].y,verts[idxtab->indices[1]].z,verts[idxtab->indices[1]].w);
+		t[2].p = Pt3(verts[idxtab->indices[2]].x,verts[idxtab->indices[2]].y,verts[idxtab->indices[2]].z,verts[idxtab->indices[2]].w);
+
+		idxtab = &(*ep)->indextab[OBJVNormal];
+		if(idxtab->nindex == 3){
+			t[0].n = Vec3(nverts[idxtab->indices[0]].i, nverts[idxtab->indices[0]].j, nverts[idxtab->indices[0]].k);
+			t[0].n = normvec3(t[0].n);
+			t[1].n = Vec3(nverts[idxtab->indices[1]].i, nverts[idxtab->indices[1]].j, nverts[idxtab->indices[1]].k);
+			t[1].n = normvec3(t[1].n);
+			t[2].n = Vec3(nverts[idxtab->indices[2]].i, nverts[idxtab->indices[2]].j, nverts[idxtab->indices[2]].k);
+			t[2].n = normvec3(t[2].n);
+		}else{
+			n = normvec3(crossvec3(subpt3(t[2].p, t[0].p), subpt3(t[1].p, t[0].p)));
+			t[0].n = t[1].n = t[2].n = mulpt3(n, -1);
+		}
+
+		vsp.p = &t[0].p;
+		vsp.n = &t[0].n;
+		vsp.idx = 0;
+		t[0].p = params->vshader(&vsp);
+		vsp.p = &t[1].p;
+		vsp.n = &t[1].n;
+		vsp.idx = 1;
+		t[1].p = params->vshader(&vsp);
+		vsp.p = &t[2].p;
+		vsp.n = &t[2].n;
+		vsp.idx = 2;
+		t[2].p = params->vshader(&vsp);
+
+		idxtab = &(*ep)->indextab[OBJVTexture];
+		if(params->modeltex != nil && idxtab->nindex == 3){
+			t[0].uv = Pt2(tverts[idxtab->indices[0]].u, tverts[idxtab->indices[0]].v, 1);
+			t[1].uv = Pt2(tverts[idxtab->indices[1]].u, tverts[idxtab->indices[1]].v, 1);
+			t[2].uv = Pt2(tverts[idxtab->indices[2]].u, tverts[idxtab->indices[2]].v, 1);
+		}else{
+			t[0].uv = t[1].uv = t[2].uv = Vec2(0,0);
+		}
+
+		rasterize(params, t, frag);
+	}
+
+	freememimage(frag);
+	sendp(params->donec, nil);
+	free(params);
+	threadexits(nil);
+}
+
+void
+shade(Framebuf *fb, OBJ *model, Memimage *modeltex, Shader *s, ulong nprocs)
+{
+	static int nparts, nworkers;
+	static OBJElem **elems = nil;
+	OBJElem *trielems[2];
+	int i, nelems;
+	uvlong time;
+	OBJObject *o;
+	OBJElem *e;
+	OBJIndexArray *idxtab;
+	SUparams *params;
+	Channel *donec;
+
+	if(elems == nil){
+		nelems = 0;
+		for(i = 0; i < nelem(model->objtab); i++)
+			for(o = model->objtab[i]; o != nil; o = o->next)
+				for(e = o->child; e != nil; e = e->next){
+					idxtab = &e->indextab[OBJVGeometric];
+					/* discard non-triangles */
+					if(e->type != OBJEFace || (idxtab->nindex != 3 && idxtab->nindex != 4))
+						continue;
+					if(idxtab->nindex == 4){
+						triangulate(trielems, e);
+						nelems += 2;
+						elems = erealloc(elems, nelems*sizeof(*elems));
+						elems[nelems-2] = trielems[0];
+						elems[nelems-1] = trielems[1];
+					}else{
+						elems = erealloc(elems, ++nelems*sizeof(*elems));
+						elems[nelems-1] = e;
+					}
+				}
+		if(nelems < nprocs){
+			nworkers = nelems;
+			nparts = 1;
+		}else{
+			nworkers = nprocs;
+			nparts = nelems/nprocs;
+		}
+	}
+	time = nanosec();
+
+	donec = chancreate(sizeof(void*), 0);
+
+	for(i = 0; i < nworkers; i++){
+		params = emalloc(sizeof *params);
+		params->fb = fb;
+		params->b = &elems[i*nparts];
+		params->e = params->b + nparts;
+		params->id = i;
+		params->donec = donec;
+		params->model = model;
+		params->modeltex = modeltex;
+		params->uni_time = time;
+		params->vshader = s->vshader;
+		params->fshader = s->fshader;
+		proccreate(shaderunit, params, mainstacksize);
+//		fprint(2, "spawned su %d for elems [%d, %d)\n", params->id, i*nparts, i*nparts+nparts);
+	}
+
+	while(i--)
+		recvp(donec);
+	chanfree(donec);
 }
--- /dev/null
+++ b/shadeop.c
@@ -1,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
+
+double
+step(double edge, double n)
+{
+	if(n < edge)
+		return 0;
+	return 1;
+}
+
+double
+smoothstep(double edge0, double edge1, double n)
+{
+	double t;
+
+	t = fclamp((n-edge0)/(edge1-edge0), 0, 1);
+	return t*t * (3 - 2*t);
+}
--- a/triangle.c
+++ /dev/null
@@ -1,42 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <geometry.h>
-#include <graphics.h>
-
-Triangle
-Trian(int x0, int y0, int x1, int y1, int x2, int y2)
-{
-	return (Triangle){Pt(x0, y0), Pt(x1, y1), Pt(x2, y2)};
-}
-
-Triangle
-Trianpt(Point p0, Point p1, Point p2)
-{
-	return (Triangle){p0, p1, p2};
-};
-
-void
-triangle(Image *dst, Triangle t, int thick, Image *src, Point sp)
-{
-	Point pl[4];
-
-	pl[0] = t.p0;
-	pl[1] = t.p1;
-	pl[2] = t.p2;
-	pl[3] = pl[0];
-
-	poly(dst, pl, nelem(pl), 0, 0, thick, src, sp);
-}
-
-void
-filltriangle(Image *dst, Triangle t, Image *src, Point sp)
-{
-	Point pl[3];
-
-	pl[0] = t.p0;
-	pl[1] = t.p1;
-	pl[2] = t.p2;
-
-	fillpoly(dst, pl, nelem(pl), 0, src, sp);
-}
--- /dev/null
+++ b/util.c
@@ -1,0 +1,108 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
+
+int
+min(int a, int b)
+{
+	return a < b? a: b;
+}
+
+int
+max(int a, int b)
+{
+	return a > b? a: b;
+}
+
+double
+fmin(double a, double b)
+{
+	return a < b? a: b;
+}
+
+double
+fmax(double a, double b)
+{
+	return a > b? a: b;
+}
+
+void
+memsetd(double *p, double v, usize len)
+{
+	double *dp;
+
+	for(dp = p; dp < p+len; dp++)
+		*dp = v;
+}
+
+Memimage *
+rgb(ulong c)
+{
+	Memimage *i;
+
+	i = eallocmemimage(UR, screen->chan);
+	i->flags |= Frepl;
+	i->clipr = Rect(-1e6, -1e6, 1e6, 1e6);
+	memfillcolor(i, c);
+	return i;
+}
+
+static void
+decproc(void *arg)
+{
+	char buf[32];
+	Deco *d;
+
+	d = arg;
+
+	close(d->pfd[0]);
+	dup(d->infd, 0);
+	close(d->infd);
+	dup(d->pfd[1], 1);
+	close(d->pfd[1]);
+
+	snprint(buf, sizeof buf, "/bin/%s", d->prog);
+
+	execl(buf, d->prog, "-9t", nil);
+	threadexitsall("execl: %r");
+}
+
+static Memimage *
+genreadimage(char *prog, char *path)
+{
+	Memimage *i;
+	Deco d;
+
+	d.prog = prog;
+
+	if(pipe(d.pfd) < 0)
+		sysfatal("pipe: %r");
+	d.infd = open(path, OREAD);
+	if(d.infd < 0)
+		sysfatal("open: %r");
+	procrfork(decproc, &d, mainstacksize, RFFDG|RFNAMEG|RFNOTEG);
+	close(d.pfd[1]);
+	i = readmemimage(d.pfd[0]);
+	close(d.pfd[0]);
+	close(d.infd);
+
+	return i;
+}
+
+Memimage *
+readtga(char *path)
+{
+	return genreadimage("tga", path);
+}
+
+Memimage *
+readpng(char *path)
+{
+	return genreadimage("png", path);
+}
--- /dev/null
+++ b/viewport.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
+
+Viewport *
+mkviewport(Rectangle r)
+{
+	Viewport *v;
+
+	v = emalloc(sizeof *v);
+	v->p = Pt2(0,0,1);
+	v->bx = Vec2(1,0);
+	v->by = Vec2(0,1);
+	v->fbctl = mkfbctl(r);
+	return v;
+}
+
+void
+rmviewport(Viewport *v)
+{
+	rmfbctl(v->fbctl);
+	free(v);
+}