shithub: libgraphics

Download patch

ref: 04d50b8bbe324fdb7a8a1e724fbc59c4050862f0
parent: 45c21bb2483adf0ad1dd2f9950681f6a07a14b24
author: rodri <[email protected]>
date: Sat Jul 13 06:23:52 EDT 2024

fix the geometry glitches when moving things around.

it's possible for interactive programs to keep updating
the geometry of the scene during rendering, which caused
primitives to look deformed or shattered in the final
image.

to avoid this we take a snapshot of the current state of
things (scene and camera), and render based on that copy.

--- a/camera.c
+++ b/camera.c
@@ -106,7 +106,37 @@
 	assert(c->clip.n > 0 && c->clip.n < c->clip.f);
 }
 
+/* TODO the current camera abstraction is quite dirty, not pleasant to work with. make it better. */
+//Camera *
+//Cam(Camcfg cfg)
+//{
+//	
+//}
+
 void
+reloadcamera(Camera *c)
+{
+	double a;
+	double l, r, b, t;
+
+	verifycfg(c);
+	switch(c->projtype){
+	case ORTHOGRAPHIC:
+		r = Dx(c->vp->fbctl->fb[0]->r)/2;
+		t = Dy(c->vp->fbctl->fb[0]->r)/2;
+		l = -r;
+		b = -t;
+		orthographic(c->proj, l, r, b, t, c->clip.n, c->clip.f);
+		break;
+	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
 configcamera(Camera *c, Viewport *v, double fov, double n, double f, Projection p)
 {
 	c->vp = v;
@@ -133,29 +163,6 @@
 }
 
 void
-reloadcamera(Camera *c)
-{
-	double a;
-	double l, r, b, t;
-
-	verifycfg(c);
-	switch(c->projtype){
-	case ORTHOGRAPHIC:
-		r = Dx(c->vp->fbctl->fb[0]->r)/2;
-		t = Dy(c->vp->fbctl->fb[0]->r)/2;
-		l = -r;
-		b = -t;
-		orthographic(c->proj, l, r, b, t, c->clip.n, c->clip.f);
-		break;
-	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, Shadertab *s)
 {
 	static Scene *skyboxscene;
@@ -167,8 +174,9 @@
 	job = emalloc(sizeof *job);
 	memset(job, 0, sizeof *job);
 	job->fb = c->vp->fbctl->getbb(c->vp->fbctl);
-	job->camera = c;
-	job->scene = c->scene;
+	job->camera = emalloc(sizeof *c);
+	*job->camera = *c;
+	job->scene = dupscene(c->scene);	/* take a snapshot */
 	job->shaders = s;
 	job->donec = chancreate(sizeof(void*), 0);
 
@@ -176,21 +184,23 @@
 	t0 = nanosec();
 	sendp(c->rctl->c, job);
 	recvp(job->donec);
+	delscene(job->scene);			/* destroy the snapshot */
 	/*
 	 * if the scene has a skybox, do another render pass,
-	 * filling in the fragments left untouched by the z-buffer.
+	 * filling in the pixels left untouched.
 	 */
 	if(c->scene->skybox != nil){
 		if(skyboxscene == nil){
 			skyboxscene = newscene("skybox");
 			mdl = mkskyboxmodel();
-			skyboxscene->addent(skyboxscene, newentity(mdl));
+			skyboxscene->addent(skyboxscene, newentity("skybox", mdl));
 		}
 		skyboxscene->skybox = c->scene->skybox;
-		job->scene = skyboxscene;
+		job->scene = dupscene(skyboxscene);
 		job->shaders = &skyboxshader;
 		sendp(c->rctl->c, job);
 		recvp(job->donec);
+		delscene(job->scene);
 	}
 	t1 = nanosec();
 	c->vp->fbctl->swap(c->vp->fbctl);
@@ -199,5 +209,6 @@
 	updatetimes(c, job);
 
 	chanfree(job->donec);
+	free(job->camera);
 	free(job);
 }
--- a/clip.c
+++ b/clip.c
@@ -75,7 +75,7 @@
  * 	- https://github.com/aap/librw/blob/14dab85dcae6f3762fb2b1eda4d58d8e67541330/tools/playground/tl_tests.cpp#L522
  */
 int
-clipprimitive(Primitive *p)
+clipprimitive(Primitive *p, Primitive *cp)
 {
 	/* signed distance from each clipping plane */
 	static double sdm[6][4] = {
@@ -97,8 +97,8 @@
 	Vout = &Voutp;
 	memset(Vin, 0, sizeof Vinp);
 	memset(Vout, 0, sizeof Voutp);
-	for(i = 0; i < p[0].type+1; i++)
-		addvert(Vin, p[0].v[i]);
+	for(i = 0; i < p->type+1; i++)
+		addvert(Vin, p->v[i]);
 
 	for(j = 0; j < 6 && Vin->n > 0; j++){
 		for(i = 0; i < Vin->n; i++){
@@ -133,10 +133,11 @@
 
 	if(Vout->n < 2)
 		cleanpoly(Vout);
-	else switch(p[0].type){
+	else switch(p->type){
 	case PLine:
-		p[0].v[0] = dupvertex(&Vout->v[0]);
-		p[0].v[1] = eqpt3(Vout->v[0].p, Vout->v[1].p)? dupvertex(&Vout->v[2]): dupvertex(&Vout->v[1]);
+		cp[0] = *p;
+		cp[0].v[0] = dupvertex(&Vout->v[0]);
+		cp[0].v[1] = eqpt3(Vout->v[0].p, Vout->v[1].p)? dupvertex(&Vout->v[2]): dupvertex(&Vout->v[1]);
 		cleanpoly(Vout);
 		np = 1;
 		break;
@@ -148,10 +149,10 @@
 			 * are referenced on every triangle, so duplicate them
 			 * to avoid complications during rasterization.
 			 */
-			p[np] = p[0];
-			p[np].v[0] = i < Vout->n-2-1? dupvertex(&Vout->v[0]): Vout->v[0];
-			p[np].v[1] = Vout->v[i+1];
-			p[np].v[2] = i < Vout->n-2-1? dupvertex(&Vout->v[i+2]): Vout->v[i+2];
+			cp[np] = *p;
+			cp[np].v[0] = i < Vout->n-2-1? dupvertex(&Vout->v[0]): Vout->v[0];
+			cp[np].v[1] = Vout->v[i+1];
+			cp[np].v[2] = i < Vout->n-2-1? dupvertex(&Vout->v[i+2]): Vout->v[i+2];
 		}
 		break;
 	}
--- a/graphics.h
+++ b/graphics.h
@@ -156,6 +156,7 @@
 struct Entity
 {
 	RFrame3;
+	char *name;
 	Model *mdl;
 
 	Entity *prev, *next;
@@ -294,10 +295,10 @@
 };
 
 /* camera */
+void reloadcamera(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*, Shadertab*);
 
 /* viewport */
@@ -326,10 +327,13 @@
 /* scene */
 int loadobjmodel(Model*, OBJ*);
 Model *newmodel(void);
+Model *dupmodel(Model*);
 void delmodel(Model*);
-Entity *newentity(Model*);
+Entity *newentity(char*, Model*);
+Entity *dupentity(Entity*);
 void delentity(Entity*);
 Scene *newscene(char*);
+Scene *dupscene(Scene*);
 void delscene(Scene*);
 void clearscene(Scene*);
 
@@ -339,6 +343,7 @@
 
 /* texture */
 Texture *alloctexture(int, Memimage*);
+Texture *duptexture(Texture*);
 void freetexture(Texture*);
 Color neartexsampler(Texture*, Point2);
 Color bilitexsampler(Texture*, Point2);
@@ -353,6 +358,7 @@
 Point2 modulapt2(Point2, Point2);
 Point3 modulapt3(Point3, Point3);
 Memimage *rgb(ulong);
+Memimage *dupmemimage(Memimage*);
 
 /* color */
 Color srgb2linear(Color);
--- a/internal.h
+++ b/internal.h
@@ -51,7 +51,7 @@
 void fprintvattrs(int, Vertex*);
 
 /* clip */
-int clipprimitive(Primitive*);
+int clipprimitive(Primitive*, Primitive*);
 int rectclipline(Rectangle, Point*, Point*);
 
 /* util */
--- a/render.c
+++ b/render.c
@@ -67,13 +67,13 @@
 }
 
 static int
-isfacingback(Primitive p)
+isfacingback(Primitive *p)
 {
 	double sa;	/* signed area */
 
-	sa = p.v[0].p.x * p.v[1].p.y - p.v[0].p.y * p.v[1].p.x +
-	     p.v[1].p.x * p.v[2].p.y - p.v[1].p.y * p.v[2].p.x +
-	     p.v[2].p.x * p.v[0].p.y - p.v[2].p.y * p.v[0].p.x;
+	sa = p->v[0].p.x * p->v[1].p.y - p->v[0].p.y * p->v[1].p.x +
+	     p->v[1].p.x * p->v[2].p.y - p->v[1].p.y * p->v[2].p.x +
+	     p->v[2].p.x * p->v[0].p.y - p->v[2].p.y * p->v[0].p.x;
 	return sa <= 0;
 }
 
@@ -277,7 +277,7 @@
 	SUparams *params, *newparams;
 	Rastertask *task;
 	VSparams vsp;
-	Primitive *ep, *p;	/* primitives to raster */
+	Primitive *ep, *cp, *p;		/* primitives to raster */
 	Rectangle *wr, bbox;
 	Channel **taskchans;
 	ulong Δy, nproc;
@@ -285,7 +285,7 @@
 	uvlong t0;
 
 	tp = arg;
-	p = emalloc(sizeof(*p)*16);
+	cp = emalloc(sizeof(*cp)*16);
 	taskchans = tp->taskchans;
 	nproc = tp->nproc;
 	wr = emalloc(nproc*sizeof(Rectangle));
@@ -323,27 +323,27 @@
 		for(ep = params->eb; ep != params->ee; ep++){
 			np = 1;	/* start with one. after clipping it might change */
 
-			*p = *ep;
-			switch(ep->type){
+			p = ep;
+			switch(p->type){
 			case PPoint:
-				p[0].v[0].mtl = ep->mtl;
-				p[0].v[0].attrs = nil;
-				p[0].v[0].nattrs = 0;
+				p->v[0].mtl = p->mtl;
+				p->v[0].attrs = nil;
+				p->v[0].nattrs = 0;
 
-				vsp.v = &p[0].v[0];
+				vsp.v = &p->v[0];
 				vsp.idx = 0;
-				p[0].v[0].p = params->vshader(&vsp);
+				p->v[0].p = params->vshader(&vsp);
 
-				if(!isvisible(p[0].v[0].p))
+				if(!isvisible(p->v[0].p))
 					break;
 
-				p[0].v[0].p = clip2ndc(p[0].v[0].p);
-				p[0].v[0].p = ndc2viewport(params->fb, p[0].v[0].p);
+				p->v[0].p = clip2ndc(p->v[0].p);
+				p->v[0].p = ndc2viewport(params->fb, p->v[0].p);
 
-				bbox.min.x = p[0].v[0].p.x;
-				bbox.min.y = p[0].v[0].p.y;
-				bbox.max.x = p[0].v[0].p.x+1;
-				bbox.max.y = p[0].v[0].p.y+1;
+				bbox.min.x = p->v[0].p.x;
+				bbox.min.y = p->v[0].p.y;
+				bbox.max.x = p->v[0].p.x+1;
+				bbox.max.y = p->v[0].p.y+1;
 
 				for(i = 0; i < nproc; i++)
 					if(rectXrect(bbox,wr[i])){
@@ -352,37 +352,39 @@
 						task = emalloc(sizeof *task);
 						task->params = newparams;
 						task->wr = wr[i];
-						task->p = p[0];
-						task->p.v[0] = dupvertex(&p[0].v[0]);
+						task->p = *p;
+						task->p.v[0] = dupvertex(&p->v[0]);
 						sendp(taskchans[i], task);
 					}
-				delvattrs(&p[0].v[0]);
+				delvattrs(&p->v[0]);
 				break;
 			case PLine:
 				for(i = 0; i < 2; i++){
-					p[0].v[i].mtl = ep->mtl;
-					p[0].v[i].attrs = nil;
-					p[0].v[i].nattrs = 0;
+					p->v[i].mtl = p->mtl;
+					p->v[i].attrs = nil;
+					p->v[i].nattrs = 0;
 
-					vsp.v = &p[0].v[i];
+					vsp.v = &p->v[i];
 					vsp.idx = i;
-					p[0].v[i].p = params->vshader(&vsp);
+					p->v[i].p = params->vshader(&vsp);
 				}
 
-				if(!isvisible(p[0].v[0].p) || !isvisible(p[0].v[1].p))
-					np = clipprimitive(p);
+				if(!isvisible(p->v[0].p) || !isvisible(p->v[1].p)){
+					np = clipprimitive(p, cp);
+					p = cp;
+				}
 
-				while(np--){
-					p[np].v[0].p = clip2ndc(p[np].v[0].p);
-					p[np].v[1].p = clip2ndc(p[np].v[1].p);
+				for(; np--; p++){
+					p->v[0].p = clip2ndc(p->v[0].p);
+					p->v[1].p = clip2ndc(p->v[1].p);
 
-					p[np].v[0].p = ndc2viewport(params->fb, p[np].v[0].p);
-					p[np].v[1].p = ndc2viewport(params->fb, p[np].v[1].p);
+					p->v[0].p = ndc2viewport(params->fb, p->v[0].p);
+					p->v[1].p = ndc2viewport(params->fb, p->v[1].p);
 
-					bbox.min.x = min(p[np].v[0].p.x, p[np].v[1].p.x);
-					bbox.min.y = min(p[np].v[0].p.y, p[np].v[1].p.y);
-					bbox.max.x = max(p[np].v[0].p.x, p[np].v[1].p.x)+1;
-					bbox.max.y = max(p[np].v[0].p.y, p[np].v[1].p.y)+1;
+					bbox.min.x = min(p->v[0].p.x, p->v[1].p.x);
+					bbox.min.y = min(p->v[0].p.y, p->v[1].p.y);
+					bbox.max.x = max(p->v[0].p.x, p->v[1].p.x)+1;
+					bbox.max.y = max(p->v[0].p.y, p->v[1].p.y)+1;
 
 					for(i = 0; i < nproc; i++)
 						if(rectXrect(bbox,wr[i])){
@@ -391,47 +393,49 @@
 							task = emalloc(sizeof *task);
 							task->params = newparams;
 							task->wr = wr[i];
-							task->p = p[np];
-							task->p.v[0] = dupvertex(&p[np].v[0]);
-							task->p.v[1] = dupvertex(&p[np].v[1]);
+							task->p = *p;
+							task->p.v[0] = dupvertex(&p->v[0]);
+							task->p.v[1] = dupvertex(&p->v[1]);
 							sendp(taskchans[i], task);
 						}
-					delvattrs(&p[np].v[0]);
-					delvattrs(&p[np].v[1]);
+					delvattrs(&p->v[0]);
+					delvattrs(&p->v[1]);
 				}
 				break;
 			case PTriangle:
 				for(i = 0; i < 3; i++){
-					p[0].v[i].mtl = p->mtl;
-					p[0].v[i].attrs = nil;
-					p[0].v[i].nattrs = 0;
-					p[0].v[i].tangent = p->tangent;
+					p->v[i].mtl = p->mtl;
+					p->v[i].attrs = nil;
+					p->v[i].nattrs = 0;
+					p->v[i].tangent = p->tangent;
 
-					vsp.v = &p[0].v[i];
+					vsp.v = &p->v[i];
 					vsp.idx = i;
-					p[0].v[i].p = params->vshader(&vsp);
+					p->v[i].p = params->vshader(&vsp);
 				}
 
-				if(!isvisible(p[0].v[0].p) || !isvisible(p[0].v[1].p) || !isvisible(p[0].v[2].p))
-					np = clipprimitive(p);
+				if(!isvisible(p->v[0].p) || !isvisible(p->v[1].p) || !isvisible(p->v[2].p)){
+					np = clipprimitive(p, cp);
+					p = cp;
+				}
 
-				while(np--){
-					p[np].v[0].p = clip2ndc(p[np].v[0].p);
-					p[np].v[1].p = clip2ndc(p[np].v[1].p);
-					p[np].v[2].p = clip2ndc(p[np].v[2].p);
+				for(; np--; p++){
+					p->v[0].p = clip2ndc(p->v[0].p);
+					p->v[1].p = clip2ndc(p->v[1].p);
+					p->v[2].p = clip2ndc(p->v[2].p);
 
 					/* culling */
-//					if(isfacingback(p[np]))
+//					if(isfacingback(*p))
 //						goto skiptri;
 
-					p[np].v[0].p = ndc2viewport(params->fb, p[np].v[0].p);
-					p[np].v[1].p = ndc2viewport(params->fb, p[np].v[1].p);
-					p[np].v[2].p = ndc2viewport(params->fb, p[np].v[2].p);
+					p->v[0].p = ndc2viewport(params->fb, p->v[0].p);
+					p->v[1].p = ndc2viewport(params->fb, p->v[1].p);
+					p->v[2].p = ndc2viewport(params->fb, p->v[2].p);
 
-					bbox.min.x = min(min(p[np].v[0].p.x, p[np].v[1].p.x), p[np].v[2].p.x);
-					bbox.min.y = min(min(p[np].v[0].p.y, p[np].v[1].p.y), p[np].v[2].p.y);
-					bbox.max.x = max(max(p[np].v[0].p.x, p[np].v[1].p.x), p[np].v[2].p.x)+1;
-					bbox.max.y = max(max(p[np].v[0].p.y, p[np].v[1].p.y), p[np].v[2].p.y)+1;
+					bbox.min.x = min(min(p->v[0].p.x, p->v[1].p.x), p->v[2].p.x);
+					bbox.min.y = min(min(p->v[0].p.y, p->v[1].p.y), p->v[2].p.y);
+					bbox.max.x = max(max(p->v[0].p.x, p->v[1].p.x), p->v[2].p.x)+1;
+					bbox.max.y = max(max(p->v[0].p.y, p->v[1].p.y), p->v[2].p.y)+1;
 
 					for(i = 0; i < nproc; i++)
 						if(rectXrect(bbox,wr[i])){
@@ -440,16 +444,16 @@
 							task = emalloc(sizeof *task);
 							task->params = newparams;
 							task->wr = wr[i];
-							task->p = p[np];
-							task->p.v[0] = dupvertex(&p[np].v[0]);
-							task->p.v[1] = dupvertex(&p[np].v[1]);
-							task->p.v[2] = dupvertex(&p[np].v[2]);
+							task->p = *p;
+							task->p.v[0] = dupvertex(&p->v[0]);
+							task->p.v[1] = dupvertex(&p->v[1]);
+							task->p.v[2] = dupvertex(&p->v[2]);
 							sendp(taskchans[i], task);
 						}
 //skiptri:
-					delvattrs(&p[np].v[0]);
-					delvattrs(&p[np].v[1]);
-					delvattrs(&p[np].v[2]);
+					delvattrs(&p->v[0]);
+					delvattrs(&p->v[1]);
+					delvattrs(&p->v[2]);
 				}
 				break;
 			default: sysfatal("alien primitive detected");
--- a/scene.c
+++ b/scene.c
@@ -166,18 +166,12 @@
 
 			if(objmtl->map_Kd != nil){
 				mtl->diffusemap = alloctexture(sRGBTexture, nil);
-				mtl->diffusemap->image = allocmemimaged(objmtl->map_Kd->r, objmtl->map_Kd->chan, objmtl->map_Kd->data);
-				if(mtl->diffusemap->image == nil)
-					sysfatal("allocmemimaged: %r");
-				mtl->diffusemap->image->data->ref++;
+				mtl->diffusemap->image = dupmemimage(objmtl->map_Kd);
 			}
 
 			if(objmtl->norm != nil){
 				mtl->normalmap = alloctexture(RAWTexture, nil);
-				mtl->normalmap->image = allocmemimaged(objmtl->norm->r, objmtl->norm->chan, objmtl->norm->data);
-				if(mtl->normalmap->image == nil)
-					sysfatal("allocmemimaged: %r");
-				mtl->normalmap->image->data->ref++;
+				mtl->normalmap->image = dupmemimage(objmtl->norm);
 			}
 
 			addmtlmap(&mtlmap, objmtl, m->nmaterials-1);
@@ -318,29 +312,61 @@
 	return m;
 }
 
-void
-delmodel(Model *m)
+Model *
+dupmodel(Model *m)
 {
+	Model *nm;
+	int i;
+
 	if(m == nil)
-		return;
+		return nil;
+
+	nm = newmodel();
 	if(m->tex != nil)
-		freetexture(m->tex);
+		nm->tex = duptexture(m->tex);
 	if(m->nmaterials > 0){
-		while(m->nmaterials--){
-			freetexture(m->materials[m->nmaterials].diffusemap);
-			freetexture(m->materials[m->nmaterials].normalmap);
-			free(m->materials[m->nmaterials].name);
+		nm->nmaterials = m->nmaterials;
+		nm->materials = emalloc(nm->nmaterials*sizeof(*nm->materials));
+		for(i = 0; i < m->nmaterials; i++){
+			nm->materials[i] = m->materials[i];
+			nm->materials[i].diffusemap = duptexture(m->materials[i].diffusemap);
+			nm->materials[i].normalmap = duptexture(m->materials[i].normalmap);
+			nm->materials[i].name = strdup(m->materials[i].name);
+			if(nm->materials[i].name == nil)
+				sysfatal("strdup: %r");
 		}
-		free(m->materials);
 	}
-	if(m->nprims > 0)
-		free(m->prims);
-	memset(m, 0, sizeof *m);
+	if(m->nprims > 0){
+		nm->nprims = m->nprims;
+		nm->prims = emalloc(nm->nprims*sizeof(*nm->prims));
+		for(i = 0; i < m->nprims; i++){
+			nm->prims[i] = m->prims[i];
+			if(nm->nmaterials > 0 && m->prims[i].mtl != nil)
+				nm->prims[i].mtl = &nm->materials[m->prims[i].mtl - m->materials];
+		}
+	}
+	return nm;
+}
+
+void
+delmodel(Model *m)
+{
+	if(m == nil)
+		return;
+
+	freetexture(m->tex);
+	while(m->nmaterials--){
+		freetexture(m->materials[m->nmaterials].diffusemap);
+		freetexture(m->materials[m->nmaterials].normalmap);
+		free(m->materials[m->nmaterials].name);
+	}
+	free(m->materials);
+	free(m->prims);
 	free(m);
 }
 
 Entity *
-newentity(Model *m)
+newentity(char *name, Model *m)
 {
 	Entity *e;
 
@@ -349,19 +375,37 @@
 	e->bx = Vec3(1,0,0);
 	e->by = Vec3(0,1,0);
 	e->bz = Vec3(0,0,1);
+	e->name = name == nil? nil: strdup(name);
 	e->mdl = m;
 	e->prev = e->next = nil;
 	return e;
 }
 
+Entity *
+dupentity(Entity *e)
+{
+	Entity *ne;
+
+	if(e == nil)
+		return nil;
+
+	ne = newentity(nil, nil);
+	*ne = *e;
+	if(e->name != nil)
+		ne->name = strdup(e->name);
+	ne->mdl = dupmodel(e->mdl);
+	ne->prev = ne->next = nil;
+	return ne;
+}
+
 void
 delentity(Entity *e)
 {
 	if(e == nil)
 		return;
-	if(e->mdl != nil)
-		delmodel(e->mdl);
-	memset(e, 0, sizeof *e);
+
+	delmodel(e->mdl);
+	free(e->name);
 	free(e);
 }
 
@@ -399,15 +443,20 @@
 	return s;
 }
 
-void
-delscene(Scene *s)
+Scene *
+dupscene(Scene *s)
 {
+	Scene *ns;
+	Entity *e;
+
 	if(s == nil)
-		return;
-	clearscene(s);
-	free(s->name);
-	memset(s, 0, sizeof *s);
-	free(s);
+		return nil;
+
+	ns = newscene(s->name);
+	if(s->nents > 0)
+		for(e = s->ents.next; e != &s->ents; e = e->next)
+			ns->addent(ns, dupentity(e));
+	return ns;
 }
 
 void
@@ -420,6 +469,16 @@
 		s->delent(s, e);
 		delentity(e);
 	}
-	if(s->skybox != nil)
-		freecubemap(s->skybox);
+	freecubemap(s->skybox);
+}
+
+void
+delscene(Scene *s)
+{
+	if(s == nil)
+		return;
+
+	clearscene(s);
+	free(s->name);
+	free(s);
 }
--- a/texture.c
+++ b/texture.c
@@ -122,9 +122,25 @@
 	return t;
 }
 
+Texture *
+duptexture(Texture *t)
+{
+	Texture *nt;
+
+	if(t == nil)
+		return nil;
+
+	nt = alloctexture(t->type, nil);
+	nt->image = dupmemimage(t->image);
+	return nt;
+}
+
 void
 freetexture(Texture *t)
 {
+	if(t == nil)
+		return;
+
 	freememimage(t->image);
 	free(t);
 }
@@ -160,6 +176,9 @@
 freecubemap(Cubemap *cm)
 {
 	int i;
+
+	if(cm == nil)
+		return;
 
 	for(i = 0; i < 6; i++)
 		freetexture(cm->faces[i]);
--- a/util.c
+++ b/util.c
@@ -84,3 +84,18 @@
 	memfillcolor(i, c);
 	return i;
 }
+
+Memimage *
+dupmemimage(Memimage *i)
+{
+	Memimage *ni;
+
+	if(i == nil)
+		return nil;
+
+	ni = allocmemimaged(i->r, i->chan, i->data);
+	if(ni == nil)
+		sysfatal("allocmemimaged: %r");
+	ni->data->ref++;
+	return ni;
+}