shithub: libgraphics

Download patch

ref: 09ffa3cfc72a10e82c5c68f4010d1c518f0c3328
parent: 8806aad87f7be2d2e82c7db2b9f0978246e5a747
author: rodri <rgl@antares-labs.eu>
date: Wed Sep 11 16:48:13 EDT 2024

change the raster format to RGBA32. draw to any image format. clean up.

got rid of the bullshit dance between XRGB32 and RGBA32, now
all rasters are RGBA32 and premultiply alpha before loading
up an image and drawing over the destination. this lets the
user compose their own scenes with correct transparency.

no more mediocre clearcolor.

as a consequence drawing got slower, but if i get the turbo
drawing pool working properly that should go away.

textures also take alpha pre-multiplied channels into account,
dividing them when sampling, so loading transparent textures
will show the correct colors.

--- a/camera.c
+++ b/camera.c
@@ -241,7 +241,7 @@
 	job->shaders = s;
 	job->donec = chancreate(sizeof(void*), 0);
 
-	fbctl->reset(fbctl, c->clearcolor);
+	fbctl->reset(fbctl);
 	t0 = nanosec();
 	sendp(c->rctl->jobq, job);
 	recvp(job->donec);
--- a/color.c
+++ b/color.c
@@ -8,6 +8,39 @@
 #include "graphics.h"
 #include "internal.h"
 
+ulong
+col2ul(Color c)
+{
+	uchar cbuf[4];
+
+	cbuf[0] = fclamp(c.a, 0, 1)*0xFF;
+	cbuf[1] = fclamp(c.b, 0, 1)*0xFF;
+	cbuf[2] = fclamp(c.g, 0, 1)*0xFF;
+	cbuf[3] = fclamp(c.r, 0, 1)*0xFF;
+	return cbuf[3]<<24 | cbuf[2]<<16 | cbuf[1]<<8 | cbuf[0];
+}
+
+Color
+ul2col(ulong l)
+{
+	Color c;
+
+	c.a = (l     & 0xff)/255.0;
+	c.b = (l>>8  & 0xff)/255.0;
+	c.g = (l>>16 & 0xff)/255.0;
+	c.r = (l>>24 & 0xff)/255.0;
+	return c;
+}
+
+int
+hasalpha(ulong chan)
+{
+	for(; chan; chan >>= 8)
+		if(TYPE(chan) == CAlpha)
+			return 1;
+	return 0;
+}
+
 /*
  * see also “The Importance of Being Linear”, Gritz and d'Eon, GPU Gems 3, Ch. 24, December 2007
  */
@@ -139,13 +172,6 @@
 	c.g = _linear2srgb(c.g);
 	c.b = _linear2srgb(c.b);
 	return c;
-}
-
-ulong
-rgba2xrgb(ulong c)
-{
-	return (c & 0xFF)<<24|(c>>24 & 0xFF)<<16|
-		(c>>16 & 0xFF)<<8|(c>>8 & 0xFF);
 }
 
 
--- a/fb.c
+++ b/fb.c
@@ -95,6 +95,23 @@
 }
 
 static void
+premulalpha(Raster *r)
+{
+	Color c;
+	ulong *p, len;
+
+	len = Dx(r->r)*Dy(r->r);
+	p = r->data;
+	while(len--){
+		c = ul2col(*p);
+		c.r *= c.a;
+		c.g *= c.a;
+		c.b *= c.a;
+		*p++ = col2ul(c);
+	}
+}
+
+static void
 fb_createraster(Framebuf *fb, char *name, ulong chan)
 {
 	Raster *r;
@@ -135,11 +152,15 @@
 	void (*filterfn)(ulong*, Raster*, Point);
 	Rectangle blkr;
 	Point sp, dp;
+	Image *tmp;
 	ulong *blk;
 
 	filterfn = nil;
 	blk = emalloc(scale.x*scale.y*4);
 	blkr = Rect(0,0,scale.x,scale.y);
+	tmp = allocimage(display, dst->r, RGBA32, 0, DNofill);
+	if(tmp == nil)
+		sysfatal("allocimage: %r");
 
 	switch(filter){
 	case UFScale2x:
@@ -158,8 +179,10 @@
 			filterfn(blk, fb, sp);
 		else
 			memsetl(blk, getpixel(fb, sp), scale.x*scale.y);
-		loadimage(dst, rectaddpt(blkr, dp), (uchar*)blk, scale.x*scale.y*4);
+		loadimage(tmp, rectaddpt(blkr, dp), (uchar*)blk, scale.x*scale.y*4);
 	}
+	draw(dst, dst->r, tmp, nil, tmp->r.min);
+	freeimage(tmp);
 	free(blk);
 }
 
@@ -186,6 +209,7 @@
 	Framebuf *fb;
 	Raster *r, *r2;
 	Rectangle sr, dr;
+	Image *tmp;
 
 	qlock(ctl);
 	fb = ctl->getfb(ctl);
@@ -203,10 +227,18 @@
 		r = r2;
 	}
 
+	/* this means the raster is a color one, so duplicate it */
+	if(r2 == nil){
+		r2 = allocraster(nil, r->r, COLOR32);
+		memmove(r2->data, r->data, Dx(r->r)*Dy(r->r)*4);
+		r = r2;
+	}
+	premulalpha(r);
+
 	if(scale.x > 1 || scale.y > 1){
 		upscaledraw(r, dst, off, scale, ctl->upfilter);
 		qunlock(ctl);
-		if(r2 != nil) freeraster(r2);
+		freeraster(r2);
 		return;
 	}
 
@@ -259,17 +291,30 @@
 //		}
 //		procpoolwait(turbodrawingpool);
 //		free(tasks);
-		loadimage(dst, rectaddpt(sr, dst->r.min), (uchar*)r->data, Dx(fb->r)*Dy(r->r)*4);
+
+		tmp = allocimage(display, sr, RGBA32, 0, DNofill);
+		if(tmp == nil)
+			sysfatal("allocimage: %r");
+
+		loadimage(tmp, sr, (uchar*)r->data, Dx(fb->r)*Dy(r->r)*4);
+		draw(dst, rectaddpt(sr, dst->r.min), tmp, nil, ZP);
+		freeimage(tmp);
 	}else if(rectclip(&sr, dr)){
+		tmp = allocimage(display, sr, RGBA32, 0, DNofill);
+		if(tmp == nil)
+			sysfatal("allocimage: %r");
+
 		dr = sr;
 		dr.max.y = dr.min.y + 1;
 		/* remove offset to get the actual rect within the framebuffer */
 		sr = rectsubpt(sr, off);
 		for(; sr.min.y < sr.max.y; sr.min.y++, dr.min.y++, dr.max.y++)
-			loadimage(dst, rectaddpt(dr, dst->r.min), rasterbyteaddr(r, sr.min), Dx(dr)*4);
+			loadimage(tmp, rectaddpt(dr, dst->r.min), rasterbyteaddr(r, sr.min), Dx(dr)*4);
+		draw(dst, rectaddpt(tmp->r, dst->r.min), tmp, nil, tmp->r.min);
+		freeimage(tmp);
 	}
 	qunlock(ctl);
-	if(r2 != nil) freeraster(r2);
+	freeraster(r2);
 }
 
 static void
@@ -278,11 +323,15 @@
 	void (*filterfn)(ulong*, Raster*, Point);
 	Rectangle blkr;
 	Point sp, dp;
+	Memimage *tmp;
 	ulong *blk;
 
 	filterfn = nil;
 	blk = emalloc(scale.x*scale.y*4);
 	blkr = Rect(0,0,scale.x,scale.y);
+	tmp = allocmemimage(dst->r, RGBA32);
+	if(tmp == nil)
+		sysfatal("allocmemimage: %r");
 
 	switch(filter){
 	case UFScale2x:
@@ -301,8 +350,10 @@
 			filterfn(blk, fb, sp);
 		else
 			memsetl(blk, getpixel(fb, sp), scale.x*scale.y);
-		loadmemimage(dst, rectaddpt(blkr, dp), (uchar*)blk, scale.x*scale.y*4);
+		loadmemimage(tmp, rectaddpt(blkr, dp), (uchar*)blk, scale.x*scale.y*4);
 	}
+	memimagedraw(dst, dst->r, tmp, tmp->r.min, nil, ZP, S);
+	freememimage(tmp);
 	free(blk);
 }
 
@@ -312,6 +363,7 @@
 	Framebuf *fb;
 	Raster *r, *r2;
 	Rectangle sr, dr;
+	Memimage *tmp;
 
 	qlock(ctl);
 	fb = ctl->getfb(ctl);
@@ -329,27 +381,47 @@
 		r = r2;
 	}
 
+	/* this means the raster is a color one, so duplicate it */
+	if(r2 == nil){
+		r2 = allocraster(nil, r->r, COLOR32);
+		memmove(r2->data, r->data, Dx(r->r)*Dy(r->r)*4);
+		r = r2;
+	}
+	premulalpha(r);
+
 	if(scale.x > 1 || scale.y > 1){
 		upscalememdraw(r, dst, off, scale, ctl->upfilter);
 		qunlock(ctl);
-		if(r2 != nil) freeraster(r2);
+		freeraster(r2);
 		return;
 	}
 
 	sr = rectaddpt(fb->r, off);
 	dr = rectsubpt(dst->r, dst->r.min);
-	if(rectinrect(sr, dr))
-		loadmemimage(dst, rectaddpt(sr, dst->r.min), (uchar*)r->data, Dx(fb->r)*Dy(r->r)*4);
-	else if(rectclip(&sr, dr)){
+	if(rectinrect(sr, dr)){
+		tmp = allocmemimage(sr, RGBA32);
+		if(tmp == nil)
+			sysfatal("allocmemimage: %r");
+
+		loadmemimage(tmp, sr, (uchar*)r->data, Dx(fb->r)*Dy(r->r)*4);
+		memimagedraw(dst, rectaddpt(sr, dst->r.min), tmp, ZP, nil, ZP, S);
+		freememimage(tmp);
+	}else if(rectclip(&sr, dr)){
+		tmp = allocmemimage(sr, RGBA32);
+		if(tmp == nil)
+			sysfatal("allocmemimage: %r");
+
 		dr = sr;
 		dr.max.y = dr.min.y + 1;
 		/* remove offset to get the actual rect within the framebuffer */
 		sr = rectsubpt(sr, off);
 		for(; sr.min.y < sr.max.y; sr.min.y++, dr.min.y++, dr.max.y++)
-			loadmemimage(dst, rectaddpt(dr, dst->r.min), rasterbyteaddr(r, sr.min), Dx(dr)*4);
+			loadmemimage(tmp, rectaddpt(dr, dst->r.min), rasterbyteaddr(r, sr.min), Dx(dr)*4);
+		memimagedraw(dst, rectaddpt(tmp->r, dst->r.min), tmp, tmp->r.min, nil, ZP, S);
+		freememimage(tmp);
 	}
 	qunlock(ctl);
-	if(r2 != nil) freeraster(r2);
+	freeraster(r2);
 }
 
 static void
@@ -371,7 +443,7 @@
 }
 
 static void
-framebufctl_reset(Framebufctl *ctl, ulong clr)
+framebufctl_reset(Framebufctl *ctl)
 {
 	Framebuf *fb;
 	Raster *r;
@@ -381,7 +453,7 @@
 	resetAbuf(&fb->abuf);
 
 	r = fb->rasters;		/* color buffer */
-	clearraster(r, rgba2xrgb(clr));
+	clearraster(r, 0);
 	r = r->next;			/* z-buffer */
 	fclearraster(r, Inf(-1));
 	while((r = r->next) != nil)
@@ -515,6 +587,8 @@
 void
 freeraster(Raster *r)
 {
+	if(r == nil)
+		return;
 	free(r->data);
 	free(r->name);
 	free(r);
--- a/graphics.h
+++ b/graphics.h
@@ -24,8 +24,8 @@
 	LightSpot,
 
 	/* raster formats */
-	COLOR32 = 0,
-	FLOAT32,
+	COLOR32 = 0,	/* RGBA32 */
+	FLOAT32,	/* F32 */
 
 	/* texture formats */
 	RAWTexture = 0,	/* unmanaged */
@@ -314,7 +314,7 @@
 	void (*draw)(Framebufctl*, Image*, char*, Point, Point);
 	void (*memdraw)(Framebufctl*, Memimage*, char*, Point, Point);
 	void (*swap)(Framebufctl*);
-	void (*reset)(Framebufctl*, ulong);
+	void (*reset)(Framebufctl*);
 	void (*createraster)(Framebufctl*, char*, ulong);
 	Raster *(*fetchraster)(Framebufctl*, char*);
 	Framebuf *(*getfb)(Framebufctl*);
@@ -350,7 +350,6 @@
 	} clip;
 	Matrix3 proj;		/* VCS to clip space xform */
 	Projection projtype;
-	ulong clearcolor;
 	int cullmode;
 	int enableblend;
 	int enabledepth;
@@ -431,14 +430,20 @@
 
 /* util */
 Point2 modulapt2(Point2, Point2);
+Point2 minpt2(Point2, Point2);
+Point2 maxpt2(Point2, Point2);
 Point3 modulapt3(Point3, Point3);
+Point3 minpt3(Point3, Point3);
+Point3 maxpt3(Point3, Point3);
 Memimage *rgb(ulong);
 Memimage *dupmemimage(Memimage*);
 
 /* color */
+ulong col2ul(Color);
+Color ul2col(ulong);
+int hasalpha(ulong);
 Color srgb2linear(Color);
 Color linear2srgb(Color);
-ulong rgba2xrgb(ulong);
 Color aces(Color);
 Color aces2(Color);
 
--- a/render.c
+++ b/render.c
@@ -11,8 +11,6 @@
 Rectangle UR = {0,0,1,1};
 //Procpool *turbodrawingpool;
 
-static ulong col2ul(Color);
-
 static Vertexattr *
 sparams_getuniform(Shaderparams *sp, char *id)
 {
@@ -57,30 +55,6 @@
 		rasterput(r, sp->p, v);
 		break;
 	}
-}
-
-static ulong
-col2ul(Color c)
-{
-	uchar cbuf[4];
-
-	cbuf[0] = fclamp(c.b, 0, 1)*0xFF;
-	cbuf[1] = fclamp(c.g, 0, 1)*0xFF;
-	cbuf[2] = fclamp(c.r, 0, 1)*0xFF;
-	cbuf[3] = fclamp(c.a, 0, 1)*0xFF;
-	return cbuf[3]<<24 | cbuf[2]<<16 | cbuf[1]<<8 | cbuf[0];
-}
-
-static Color
-ul2col(ulong l)
-{
-	Color c;
-
-	c.b = (l     & 0xff)/255.0;
-	c.g = (l>>8  & 0xff)/255.0;
-	c.r = (l>>16 & 0xff)/255.0;
-	c.a = (l>>24 & 0xff)/255.0;
-	return c;
 }
 
 static void
--- a/texture.c
+++ b/texture.c
@@ -48,6 +48,7 @@
 	uchar cbuf[4];
 
 	switch(t->image->chan){
+	default: sysfatal("unsupported texture format");
 	case RGB24:
 		unloadmemimage(t->image, rectaddpt(UR, sp), cbuf+1, sizeof cbuf - 1);
 		cbuf[0] = 0xFF;
@@ -63,6 +64,12 @@
 	}
 
 	c = cbuf2col(cbuf);
+	/* remove pre-multiplied alpha */
+	if(hasalpha(t->image->chan) && c.a > 0 && c.a < 1){
+		c.r /= c.a;
+		c.g /= c.a;
+		c.b /= c.a;
+	}
 	switch(t->type){
 	case sRGBTexture: c = srgb2linear(c); break;
 	}
--- a/util.c
+++ b/util.c
@@ -14,10 +14,52 @@
 	return (Point2){a.x*b.x, a.y*b.y, a.w*b.w};
 }
 
+Point2
+minpt2(Point2 a, Point2 b)
+{
+	return (Point2){
+		min(a.x, b.x),
+		min(a.y, b.y),
+		min(a.w, b.w)
+	};
+}
+
+Point2
+maxpt2(Point2 a, Point2 b)
+{
+	return (Point2){
+		max(a.x, b.x),
+		max(a.y, b.y),
+		max(a.w, b.w)
+	};
+}
+
 Point3
 modulapt3(Point3 a, Point3 b)
 {
 	return (Point3){a.x*b.x, a.y*b.y, a.z*b.z, a.w*b.w};
+}
+
+Point3
+minpt3(Point3 a, Point3 b)
+{
+	return (Point3){
+		min(a.x, b.x),
+		min(a.y, b.y),
+		min(a.z, b.z),
+		min(a.w, b.w)
+	};
+}
+
+Point3
+maxpt3(Point3 a, Point3 b)
+{
+	return (Point3){
+		max(a.x, b.x),
+		max(a.y, b.y),
+		max(a.z, b.z),
+		max(a.w, b.w)
+	};
 }
 
 void