shithub: libgraphics

ref: 5a97a65584a9bdd97cd3399beff59c5ea5d9d4a9
dir: /render.c/

View raw version
#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"

Rectangle UR = {0,0,1,1};


static void
pixel(Memimage *dst, Point p, Memimage *src)
{
	if(dst == nil || src == nil)
		return;

	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;
}

static int
isclipping(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 > p.w)
		return 1;
	return 0;
}

Point3
world2vcs(Camera *c, Point3 p)
{
	return rframexform3(p, *c);
}

Point3
vcs2clip(Camera *c, Point3 p)
{
	return xform3(p, c->proj);
}

Point3
world2clip(Camera *c, Point3 p)
{
	return vcs2clip(c, world2vcs(c, p));
}

static Point3
clip2ndc(Point3 p)
{
	return divpt3(p, p.w);
}

static Point3
ndc2viewport(Framebuf *fb, Point3 p)
{
	Matrix3 view = {
		Dx(fb->r)/2.0,             0,       0,       Dx(fb->r)/2.0,
		0,            -Dy(fb->r)/2.0,       0,       Dy(fb->r)/2.0,
		0,                         0, 1.0/2.0,             1.0/2.0,
		0,                         0,       0,                   1,
	};

	return xform3(p, view);
}

void
perspective(Matrix3 m, double fov, double a, double n, double f)
{
	double cotan;

	cotan = 1/tan(fov/2);
	identity3(m);
	m[0][0] =  cotan/a;
	m[1][1] =  cotan;
	m[2][2] =  (f+n)/(f-n);
	m[2][3] = -2*f*n/(f-n);
	m[3][2] = -1;
}

void
orthographic(Matrix3 m, double l, double r, double b, double t, double n, double f)
{
	identity3(m);
	m[0][0] =  2/(r - l);
	m[1][1] =  2/(t - b);
	m[2][2] = -2/(f - n);
	m[0][3] = -(r + l)/(r - l);
	m[1][3] = -(t + b)/(t - b);
	m[2][3] = -(f + n)/(f - n);
}

static void
rasterize(SUparams *params, Triangle t, Memimage *frag)
{
	FSparams fsp;
	Triangle2 t₂, tt₂;
	Rectangle bbox;
	Point p, tp;
	Point3 bc;
	double z, depth;
	uchar cbuf[4];

	t₂.p0 = Pt2(t[0].p.x, t[0].p.y, 1);
	t₂.p1 = Pt2(t[1].p.x, t[1].p.y, 1);
	t₂.p2 = Pt2(t[2].p.x, t[2].p.y, 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;
			depth = fclamp(z, 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));
		}
}

static void
shaderunit(void *arg)
{
	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.v = &t[0];
		vsp.idx = 0;
		t[0].p = params->vshader(&vsp);
		vsp.v = &t[1];
		vsp.idx = 1;
		t[1].p = params->vshader(&vsp);
		vsp.v = &t[2];
		vsp.idx = 2;
		t[2].p = params->vshader(&vsp);

//		if(isclipping(t[0].p) || isclipping(t[1].p) || isclipping(t[2].p))
//			continue;	/* TODO clip the primitive */

		t[0].p = ndc2viewport(params->fb, clip2ndc(t[0].p));
		t[1].p = ndc2viewport(params->fb, clip2ndc(t[1].p));
		t[2].p = ndc2viewport(params->fb, clip2ndc(t[2].p));

		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);
}