diff --git a/.gitignore b/.gitignore
index e128382..b2af370 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ texture
# Downloaded files
examples/stb_image.h
examples/stb_image_write.h
+*.d
diff --git a/examples/raw/README.md b/examples/raw/README.md
new file mode 100644
index 0000000..a936943
--- /dev/null
+++ b/examples/raw/README.md
@@ -0,0 +1,19 @@
+# Demos of raw usages
+
+## `alpha.c`: alpha channel
+
+Illustrates alpha channel support by rendering a PNG with the following shapes:
+
+- **Bottom half**: A red opaque rectangle with three overlapping triangles at different alpha levels — green (α=0.8, nearly opaque), blue (α=0.5, half-transparent showing the red through), and cyan (α=0.25, mostly transparent)
+- **Upper left**: A smooth-shaded magenta triangle that fades from fully opaque at the base to fully transparent at the tip — demonstrating per-vertex alpha interpolation
+- **Upper right**: A checkerboard RGBA texture where the white squares fade from transparent (bottom) to opaque (top), and the golden squares have α=200/255 — demonstrating per-texel texture alpha
+
+To run this demo:
+
+```sh
+cc -std=c99 -O2 -Iinclude -Iexamples examples/raw/alpha.c lib/libTinyGL.a -lm -o alpha && ./alpha
+```
+
+Then find `alpha_demo.png` in your working directory. Expected image:
+
+
diff --git a/examples/raw/alpha.c b/examples/raw/alpha.c
new file mode 100644
index 0000000..633811f
--- /dev/null
+++ b/examples/raw/alpha.c
@@ -0,0 +1,200 @@
+/*
+ * Alpha blending demo - renders to alpha_demo.png
+ *
+ * Scene: A red background with three overlapping triangles at different
+ * alpha levels, plus a textured quad with per-texel alpha.
+ */
+
+#include
+#include
+#include
+#include
+
+#include
+#include "zbuffer.h"
+
+#define STBIW_ASSERT(x)
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "stb_image_write.h"
+
+#define WIDTH 512
+#define HEIGHT 512
+#define TEX_SIZE 256
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+/* Generate a checkerboard RGBA texture with varying alpha */
+static void make_checker_texture(unsigned char *rgba)
+{
+ for (int y = 0; y < TEX_SIZE; y++) {
+ for (int x = 0; x < TEX_SIZE; x++) {
+ int idx = (y * TEX_SIZE + x) * 4;
+ int check = ((x / 32) + (y / 32)) & 1;
+ if (check) {
+ /* White squares, alpha varies by row */
+ rgba[idx + 0] = 255;
+ rgba[idx + 1] = 255;
+ rgba[idx + 2] = 255;
+ rgba[idx + 3] = (unsigned char)(255 * y / TEX_SIZE);
+ } else {
+ /* Yellow squares, full alpha */
+ rgba[idx + 0] = 255;
+ rgba[idx + 1] = 200;
+ rgba[idx + 2] = 0;
+ rgba[idx + 3] = 200;
+ }
+ }
+ }
+}
+
+int main(void)
+{
+ ZBuffer *zb;
+ unsigned char *framebuf;
+
+ /* Initialize */
+ zb = ZB_open(WIDTH, HEIGHT, ZB_MODE_RGBA, NULL);
+ if (!zb) {
+ fprintf(stderr, "Failed to open ZBuffer\n");
+ return 1;
+ }
+ glInit(zb);
+
+ glViewport(0, 0, WIDTH, HEIGHT);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ glDisable(GL_LIGHTING);
+ glDisable(GL_DEPTH_TEST);
+
+ /* Clear to dark blue background */
+ glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ /* Enable alpha blending */
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ /* --- Draw a large opaque red rectangle as background element --- */
+ glShadeModel(GL_FLAT);
+ glColor4f(0.8f, 0.2f, 0.1f, 1.0f);
+ glBegin(GL_QUADS);
+ glVertex3f(-0.9f, -0.9f, 0.0f);
+ glVertex3f( 0.9f, -0.9f, 0.0f);
+ glVertex3f( 0.9f, -0.2f, 0.0f);
+ glVertex3f(-0.9f, -0.2f, 0.0f);
+ glEnd();
+
+ /* --- Three overlapping triangles with decreasing alpha --- */
+
+ /* Green triangle, alpha=0.8 (mostly opaque) */
+ glShadeModel(GL_FLAT);
+ glColor4f(0.0f, 0.9f, 0.2f, 0.8f);
+ glBegin(GL_TRIANGLES);
+ glVertex3f(-0.7f, -0.8f, 0.0f);
+ glVertex3f( 0.0f, -0.8f, 0.0f);
+ glVertex3f(-0.35f, 0.0f, 0.0f);
+ glEnd();
+
+ /* Blue triangle, alpha=0.5 (half transparent) */
+ glColor4f(0.2f, 0.3f, 1.0f, 0.5f);
+ glBegin(GL_TRIANGLES);
+ glVertex3f(-0.3f, -0.8f, 0.0f);
+ glVertex3f( 0.4f, -0.8f, 0.0f);
+ glVertex3f( 0.05f, 0.0f, 0.0f);
+ glEnd();
+
+ /* Cyan triangle, alpha=0.25 (mostly transparent) */
+ glColor4f(0.0f, 0.9f, 0.9f, 0.25f);
+ glBegin(GL_TRIANGLES);
+ glVertex3f( 0.1f, -0.8f, 0.0f);
+ glVertex3f( 0.8f, -0.8f, 0.0f);
+ glVertex3f( 0.45f, 0.0f, 0.0f);
+ glEnd();
+
+ /* --- Smooth-shaded triangle with alpha gradient --- */
+ glShadeModel(GL_SMOOTH);
+ glBegin(GL_TRIANGLES);
+ /* Fully opaque magenta at bottom-left */
+ glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
+ glVertex3f(-0.8f, 0.1f, 0.0f);
+ /* Fully opaque magenta at bottom-right */
+ glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
+ glVertex3f( 0.0f, 0.1f, 0.0f);
+ /* Fully transparent magenta at top (fades out) */
+ glColor4f(1.0f, 0.0f, 1.0f, 0.0f);
+ glVertex3f(-0.4f, 0.9f, 0.0f);
+ glEnd();
+
+ /* --- Textured quad with per-texel alpha (checkerboard) --- */
+ {
+ static unsigned char tex_data[TEX_SIZE * TEX_SIZE * 4];
+ GLuint tex;
+
+ make_checker_texture(tex_data);
+
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, 4, TEX_SIZE, TEX_SIZE, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, tex_data);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ glEnable(GL_TEXTURE_2D);
+ glShadeModel(GL_FLAT);
+ glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ glBegin(GL_QUADS);
+ glTexCoord2f(0.0f, 0.0f); glVertex3f( 0.1f, 0.1f, 0.0f);
+ glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.9f, 0.1f, 0.0f);
+ glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.9f, 0.9f, 0.0f);
+ glTexCoord2f(0.0f, 1.0f); glVertex3f( 0.1f, 0.9f, 0.0f);
+ glEnd();
+
+ glDisable(GL_TEXTURE_2D);
+ glDeleteTextures(1, &tex);
+ }
+
+ /* --- Save to PNG --- */
+ framebuf = malloc(WIDTH * HEIGHT * 3);
+ if (!framebuf) {
+ fprintf(stderr, "Out of memory\n");
+ ZB_close(zb);
+ glClose();
+ return 1;
+ }
+
+ /* Convert ARGB framebuffer to RGB for PNG */
+ {
+ unsigned int *pbuf = (unsigned int *)zb->pbuf;
+ for (int y = 0; y < HEIGHT; y++) {
+ for (int x = 0; x < WIDTH; x++) {
+ unsigned int pix = pbuf[y * WIDTH + x];
+ int idx = (y * WIDTH + x) * 3;
+ framebuf[idx + 0] = (pix >> 16) & 0xFF; /* R */
+ framebuf[idx + 1] = (pix >> 8) & 0xFF; /* G */
+ framebuf[idx + 2] = pix & 0xFF; /* B */
+ }
+ }
+ }
+
+ if (stbi_write_png("alpha_demo.png", WIDTH, HEIGHT, 3, framebuf,
+ WIDTH * 3)) {
+ printf("Saved alpha_demo.png (%dx%d)\n", WIDTH, HEIGHT);
+ } else {
+ fprintf(stderr, "Failed to write PNG\n");
+ free(framebuf);
+ ZB_close(zb);
+ glClose();
+ return 1;
+ }
+
+ free(framebuf);
+ ZB_close(zb);
+ glClose();
+ return 0;
+}
diff --git a/include/zbuffer.h b/include/zbuffer.h
index 0e5a3d5..c38ecae 100644
--- a/include/zbuffer.h
+++ b/include/zbuffer.h
@@ -106,9 +106,16 @@ typedef GLushort PIXEL;
#endif
#if TGL_HAS(LIT_TEXTURES)
+#if TGL_FEATURE_RENDER_BITS == 32
+/* Preserve alpha from the texture pixel through the color mix */
+#define RGB_MIX_FUNC(rr, gg, bb, tpix) \
+ (RGB_TO_PIXEL(((rr * GET_RED(tpix)) >> 8), ((gg * GET_GREEN(tpix)) >> 8), \
+ ((bb * GET_BLUE(tpix)) >> 8)) | ((tpix) & 0xFF000000))
+#else
#define RGB_MIX_FUNC(rr, gg, bb, tpix) \
RGB_TO_PIXEL(((rr * GET_RED(tpix)) >> 8), ((gg * GET_GREEN(tpix)) >> 8), \
((bb * GET_BLUE(tpix)) >> 8))
+#endif
#else
#define RGB_MIX_FUNC(rr, gg, bb, tpix) (tpix)
#endif
@@ -162,23 +169,34 @@ typedef GLushort PIXEL;
break; \
}
+/* Alpha blending helper: multiply 8.16 fixed-point color by [0,255] factor.
+ * Uses (factor + (factor >> 7)) to map [0,255] -> [0,256] so that
+ * factor=255 yields identity and factor=0 yields zero. */
+#define TGL_ALPHA_MUL(val, factor) \
+ ((((val) >> 16) * ((factor) + ((factor) >> 7))) << 8)
+
#define TGL_BLEND_FUNC(source, dest) \
- {{GLuint sr, sg, sb, dr, dg, db; \
+ {{GLuint sr, sg, sb, dr, dg, db, sa; \
{ \
GLuint temp = source; \
sr = GET_REDDER(temp); \
sg = GET_GREENER(temp); \
sb = GET_BLUEER(temp); \
+ sa = (temp >> 24) & 0xFF; \
temp = dest; \
dr = GET_REDDER(temp); \
dg = GET_GREENER(temp); \
db = GET_BLUEER(temp); \
} \
- /*printf("\nShould never reach this point!");*/ \
switch (sfactor) { \
case GL_ONE: \
default: \
break; \
+ case GL_SRC_ALPHA: \
+ sr = TGL_ALPHA_MUL(sr, sa); \
+ sg = TGL_ALPHA_MUL(sg, sa); \
+ sb = TGL_ALPHA_MUL(sb, sa); \
+ break; \
case GL_ONE_MINUS_SRC_COLOR: \
sr = ~sr & COLOR_MASK; \
sg = ~sg & COLOR_MASK; \
@@ -194,6 +212,12 @@ typedef GLushort PIXEL;
case GL_ONE: \
default: \
break; \
+ case GL_ONE_MINUS_SRC_ALPHA: \
+ {GLuint inv_sa = 255 - sa; \
+ dr = TGL_ALPHA_MUL(dr, inv_sa); \
+ dg = TGL_ALPHA_MUL(dg, inv_sa); \
+ db = TGL_ALPHA_MUL(db, inv_sa);} \
+ break; \
case GL_ONE_MINUS_DST_COLOR: \
dr = ~dr & COLOR_MASK; \
dg = ~dg & COLOR_MASK; \
@@ -209,20 +233,25 @@ typedef GLushort PIXEL;
} \
}
-#define TGL_BLEND_FUNC_RGB(rr, gg, bb, dest) \
+#define TGL_BLEND_FUNC_RGB(rr, gg, bb, aa, dest) \
{{GLint sr = rr & COLOR_MASK, sg = gg & COLOR_MASK, sb = bb & COLOR_MASK, \
dr, dg, db; \
+ GLuint sa = ((aa) >> 16) & 0xFF; \
{ \
GLuint temp = dest; \
dr = GET_REDDER(temp); \
dg = GET_GREENER(temp); \
db = GET_BLUEER(temp); \
} \
- /*printf("\nShould never reach this point!");*/ \
switch (sfactor) { \
case GL_ONE: \
default: \
break; \
+ case GL_SRC_ALPHA: \
+ sr = TGL_ALPHA_MUL(sr, sa); \
+ sg = TGL_ALPHA_MUL(sg, sa); \
+ sb = TGL_ALPHA_MUL(sb, sa); \
+ break; \
case GL_ONE_MINUS_SRC_COLOR: \
sr = ~sr & COLOR_MASK; \
sg = ~sg & COLOR_MASK; \
@@ -238,6 +267,12 @@ typedef GLushort PIXEL;
case GL_ONE: \
default: \
break; \
+ case GL_ONE_MINUS_SRC_ALPHA: \
+ {GLuint inv_sa = 255 - sa; \
+ dr = TGL_ALPHA_MUL(dr, inv_sa); \
+ dg = TGL_ALPHA_MUL(dg, inv_sa); \
+ db = TGL_ALPHA_MUL(db, inv_sa);} \
+ break; \
case GL_ONE_MINUS_DST_COLOR: \
dr = ~dr & COLOR_MASK; \
dg = ~dg & COLOR_MASK; \
@@ -259,9 +294,9 @@ typedef GLushort PIXEL;
{ \
dest = source; \
}
-#define TGL_BLEND_FUNC_RGB(rr, gg, bb, dest) \
- { \
- dest = RGB_TO_PIXEL(rr, gg, bb); \
+#define TGL_BLEND_FUNC_RGB(rr, gg, bb, aa, dest) \
+ { \
+ dest = RGB_TO_PIXEL(rr, gg, bb); \
}
#endif
@@ -303,6 +338,7 @@ typedef struct {
GLint x, y, z; /* integer coordinates in the zbuffer */
GLint s, t; /* coordinates for the mapping */
GLint r, g, b; /* color indexes */
+ GLint a; /* alpha (8.16 fixed-point, same format as r,g,b) */
GLfloat sz, tz; /* temporary coordinates for mapping */
} ZBufferPoint;
diff --git a/src/image_util.c b/src/image_util.c
index b898055..54046fc 100644
--- a/src/image_util.c
+++ b/src/image_util.c
@@ -36,6 +36,24 @@ void gl_convertRGB_to_8A8R8G8B(GLuint *pixmap,
}
}
+/*
+ * Convert RGBA (4 bytes/pixel) to internal 0xAARRGGBB format,
+ * preserving the alpha channel.
+ */
+void gl_convertRGBA_to_8A8R8G8B(GLuint *pixmap,
+ GLubyte *rgba,
+ GLint xsize,
+ GLint ysize)
+{
+ GLubyte *p = rgba;
+ GLint n = xsize * ysize;
+ for (GLint i = 0; i < n; i++) {
+ pixmap[i] = (((GLuint) p[3]) << 24) | (((GLuint) p[0]) << 16) |
+ (((GLuint) p[1]) << 8) | (((GLuint) p[2]));
+ p += 4;
+ }
+}
+
/*
* linear GLinterpolation with xf,yf normalized to 2^16
*/
@@ -136,3 +154,38 @@ void gl_resizeImageNoInterpolate(GLubyte *dest,
y1 += y1inc;
}
}
+
+/* resizing RGBA (4 channels) with no interpolation */
+
+void gl_resizeImageNoInterpolate4(GLubyte *dest,
+ GLint xsize_dest,
+ GLint ysize_dest,
+ GLubyte *src,
+ GLint xsize_src,
+ GLint ysize_src)
+{
+ GLubyte *pix = dest, *pix_src = src;
+
+ GLint x1inc =
+ (GLint) ((GLfloat) ((xsize_src) << FRAC_BITS) / (GLfloat) (xsize_dest));
+ GLint y1inc =
+ (GLint) ((GLfloat) ((ysize_src) << FRAC_BITS) / (GLfloat) (ysize_dest));
+
+ GLint y1 = 0;
+ for (GLint y = 0; y < ysize_dest; y++) {
+ GLint x1 = 0;
+ for (GLint x = 0; x < xsize_dest; x++) {
+ GLint xi = x1 >> FRAC_BITS, yi = y1 >> FRAC_BITS;
+ GLubyte *pix1 = pix_src + (yi * xsize_src + xi) * 4;
+
+ pix[0] = pix1[0];
+ pix[1] = pix1[1];
+ pix[2] = pix1[2];
+ pix[3] = pix1[3];
+
+ pix += 4;
+ x1 += x1inc;
+ }
+ y1 += y1inc;
+ }
+}
diff --git a/src/texture.c b/src/texture.c
index 279292d..a27b664 100644
--- a/src/texture.c
+++ b/src/texture.c
@@ -344,26 +344,32 @@ void glopTexImage2D(GLParam *p)
void *pixels = p[9].p;
GLubyte *pixels1;
GLint do_free = 0;
+ GLint is_rgba = (format == GL_RGBA && components == 4);
GLContext *c = gl_get_context();
{
#if TGL_HAS(ERROR_CHECK)
if (!(c->current_texture != NULL && target == GL_TEXTURE_2D &&
- level == 0 && components == 3 && border == 0 &&
- format == GL_RGB && type == GL_UNSIGNED_BYTE))
+ level == 0 &&
+ ((components == 3 && format == GL_RGB) ||
+ (components == 4 && format == GL_RGBA)) &&
+ border == 0 && type == GL_UNSIGNED_BYTE))
#define ERROR_FLAG GL_INVALID_ENUM
#include "error_check.h"
#else
if (!(c->current_texture != NULL && target == GL_TEXTURE_2D &&
- level == 0 && components == 3 && border == 0 &&
- format == GL_RGB && type == GL_UNSIGNED_BYTE))
+ level == 0 &&
+ ((components == 3 && format == GL_RGB) ||
+ (components == 4 && format == GL_RGBA)) &&
+ border == 0 && type == GL_UNSIGNED_BYTE))
gl_fatal_error(
"glTexImage2D: combination of parameters not handled!!");
#endif
}
if (width != TGL_FEATURE_TEXTURE_DIM || height != TGL_FEATURE_TEXTURE_DIM) {
+ GLint bpp = is_rgba ? 4 : 3;
pixels1 = gl_malloc(TGL_FEATURE_TEXTURE_DIM * TGL_FEATURE_TEXTURE_DIM *
- 3); /* GUARDED*/
+ bpp); /* GUARDED*/
if (!pixels1) {
#if TGL_HAS(ERROR_CHECK)
#define ERROR_FLAG GL_OUT_OF_MEMORY
@@ -375,9 +381,14 @@ void glopTexImage2D(GLParam *p)
/* no GLinterpolation is done here to respect the original image
* aliasing ! */
- gl_resizeImageNoInterpolate(pixels1, TGL_FEATURE_TEXTURE_DIM,
- TGL_FEATURE_TEXTURE_DIM, pixels, width,
- height);
+ if (is_rgba)
+ gl_resizeImageNoInterpolate4(pixels1, TGL_FEATURE_TEXTURE_DIM,
+ TGL_FEATURE_TEXTURE_DIM, pixels, width,
+ height);
+ else
+ gl_resizeImageNoInterpolate(pixels1, TGL_FEATURE_TEXTURE_DIM,
+ TGL_FEATURE_TEXTURE_DIM, pixels, width,
+ height);
do_free = 1;
width = TGL_FEATURE_TEXTURE_DIM;
height = TGL_FEATURE_TEXTURE_DIM;
@@ -389,7 +400,10 @@ void glopTexImage2D(GLParam *p)
im->xsize = width;
im->ysize = height;
#if TGL_FEATURE_RENDER_BITS == 32
- gl_convertRGB_to_8A8R8G8B(im->pixmap, pixels1, width, height);
+ if (is_rgba)
+ gl_convertRGBA_to_8A8R8G8B(im->pixmap, pixels1, width, height);
+ else
+ gl_convertRGB_to_8A8R8G8B(im->pixmap, pixels1, width, height);
#elif TGL_FEATURE_RENDER_BITS == 16
gl_convertRGB_to_5R6G5B(im->pixmap, pixels1, width, height);
#else
diff --git a/src/vertex.c b/src/vertex.c
index 1b61e9d..dfadb87 100644
--- a/src/vertex.c
+++ b/src/vertex.c
@@ -158,6 +158,11 @@ static void gl_transform_to_viewport_vertex_c(GLContext *c, GLVertex *v)
(GLint) (v->color.v[2] * COLOR_CORRECTED_MULT_MASK + COLOR_MIN_MULT) &
COLOR_MASK;
+ /* Propagate alpha in the same 8.16 fixed-point format as r,g,b */
+ v->zp.a =
+ (GLint) (v->color.v[3] * COLOR_CORRECTED_MULT_MASK + COLOR_MIN_MULT) &
+ COLOR_MASK;
+
if (c->texture_2d_enabled) {
v->zp.s = (GLint) (v->tex_coord.X * (ZB_POINT_S_MAX - ZB_POINT_S_MIN) +
ZB_POINT_S_MIN);
diff --git a/src/zgl.h b/src/zgl.h
index 7c98ae6..2cb1ff6 100644
--- a/src/zgl.h
+++ b/src/zgl.h
@@ -419,6 +419,10 @@ void gl_convertRGB_to_8A8R8G8B(GLuint *pixmap,
GLubyte *rgb,
GLint xsize,
GLint ysize);
+void gl_convertRGBA_to_8A8R8G8B(GLuint *pixmap,
+ GLubyte *rgba,
+ GLint xsize,
+ GLint ysize);
void gl_resizeImage(GLubyte *dest,
GLint xsize_dest,
GLint ysize_dest,
@@ -431,6 +435,12 @@ void gl_resizeImageNoInterpolate(GLubyte *dest,
GLubyte *src,
GLint xsize_src,
GLint ysize_src);
+void gl_resizeImageNoInterpolate4(GLubyte *dest,
+ GLint xsize_dest,
+ GLint ysize_dest,
+ GLubyte *src,
+ GLint xsize_src,
+ GLint ysize_src);
void gl_fatal_error(char *format, ...);
diff --git a/src/zline.c b/src/zline.c
index d7c4d31..d23ca34 100644
--- a/src/zline.c
+++ b/src/zline.c
@@ -47,7 +47,7 @@ void ZB_plot(ZBuffer *zb, ZBufferPoint *p)
if (!zb->enable_blend)
*pp = RGB_TO_PIXEL(p->r, p->g, p->b);
else
- TGL_BLEND_FUNC_RGB(p->r, p->g, p->b, (*pp))
+ TGL_BLEND_FUNC_RGB(p->r, p->g, p->b, p->a, (*pp))
#else
*pp = RGB_TO_PIXEL(p->r, p->g, p->b);
#endif
@@ -76,7 +76,7 @@ void ZB_plot(ZBuffer *zb, ZBufferPoint *p)
if (!zb->enable_blend)
*pp = col;
else
- TGL_BLEND_FUNC_RGB(p->r, p->g, p->b, (*pp))
+ TGL_BLEND_FUNC_RGB(p->r, p->g, p->b, p->a, (*pp))
#else
*pp = col;
#endif
diff --git a/src/ztriangle.c b/src/ztriangle.c
index 33f0384..ef60064 100644
--- a/src/ztriangle.c
+++ b/src/ztriangle.c
@@ -61,6 +61,7 @@ void ZB_setTexture(ZBuffer *zb, PIXEL *texture)
/* Variant DT0_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -78,9 +79,10 @@ void ZB_fillTriangleFlat_DT0_DW0(ZBuffer *zb,
#define INTERP_Z
-#define DRAW_INIT() \
- { \
- color = RGB_TO_PIXEL(p2->r, p2->g, p2->b); \
+#define DRAW_INIT() \
+ { \
+ color = RGB_TO_PIXEL(p2->r, p2->g, p2->b) | \
+ ((GLuint)((p2->a >> 16) & 0xFF) << 24); \
}
#define PUT_PIXEL(_a) \
@@ -100,6 +102,7 @@ void ZB_fillTriangleFlat_DT0_DW0(ZBuffer *zb,
/* Variant DT0_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -117,9 +120,10 @@ void ZB_fillTriangleFlat_DT0_DW1(ZBuffer *zb,
#define INTERP_Z
-#define DRAW_INIT() \
- { \
- color = RGB_TO_PIXEL(p2->r, p2->g, p2->b); \
+#define DRAW_INIT() \
+ { \
+ color = RGB_TO_PIXEL(p2->r, p2->g, p2->b) | \
+ ((GLuint)((p2->a >> 16) & 0xFF) << 24); \
}
#define PUT_PIXEL(_a) \
@@ -140,6 +144,7 @@ void ZB_fillTriangleFlat_DT0_DW1(ZBuffer *zb,
/* Variant DT1_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -157,9 +162,10 @@ void ZB_fillTriangleFlat_DT1_DW0(ZBuffer *zb,
#define INTERP_Z
-#define DRAW_INIT() \
- { \
- color = RGB_TO_PIXEL(p2->r, p2->g, p2->b); \
+#define DRAW_INIT() \
+ { \
+ color = RGB_TO_PIXEL(p2->r, p2->g, p2->b) | \
+ ((GLuint)((p2->a >> 16) & 0xFF) << 24); \
}
#define PUT_PIXEL(_a) \
@@ -179,6 +185,7 @@ void ZB_fillTriangleFlat_DT1_DW0(ZBuffer *zb,
/* Variant DT1_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -196,9 +203,10 @@ void ZB_fillTriangleFlat_DT1_DW1(ZBuffer *zb,
#define INTERP_Z
-#define DRAW_INIT() \
- { \
- color = RGB_TO_PIXEL(p2->r, p2->g, p2->b); \
+#define DRAW_INIT() \
+ { \
+ color = RGB_TO_PIXEL(p2->r, p2->g, p2->b) | \
+ ((GLuint)((p2->a >> 16) & 0xFF) << 24); \
}
#define PUT_PIXEL(_a) \
@@ -225,6 +233,7 @@ void ZB_fillTriangleFlat_DT1_DW1(ZBuffer *zb,
/* Variant DT0_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -260,6 +269,7 @@ void ZB_fillTriangleFlatNOBLEND_DT0_DW0(ZBuffer *zb,
/* Variant DT0_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -296,6 +306,7 @@ void ZB_fillTriangleFlatNOBLEND_DT0_DW1(ZBuffer *zb,
/* Variant DT1_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -331,6 +342,7 @@ void ZB_fillTriangleFlatNOBLEND_DT1_DW0(ZBuffer *zb,
/* Variant DT1_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -375,6 +387,7 @@ void ZB_fillTriangleFlatNOBLEND_DT1_DW1(ZBuffer *zb,
/* Variant DT0_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -391,21 +404,23 @@ void ZB_fillTriangleSmooth_DT0_DW0(ZBuffer *zb,
#define INTERP_Z
#define INTERP_RGB
+#define INTERP_A
#define DRAW_INIT() \
{ \
}
-#define PUT_PIXEL(_a) \
- { \
- register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
- if (1 STIPTEST(_a)) { \
- TGL_BLEND_FUNC_RGB(or1, og1, ob1, (pp[_a])); \
- } \
- z += dzdx; \
- og1 += dgdx; \
- or1 += drdx; \
- ob1 += dbdx; \
+#define PUT_PIXEL(_a) \
+ { \
+ register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
+ if (1 STIPTEST(_a)) { \
+ TGL_BLEND_FUNC_RGB(or1, og1, ob1, oa1, (pp[_a])); \
+ } \
+ z += dzdx; \
+ og1 += dgdx; \
+ or1 += drdx; \
+ ob1 += dbdx; \
+ oa1 += dadx; \
}
#include "ztriangle.h"
@@ -414,6 +429,7 @@ void ZB_fillTriangleSmooth_DT0_DW0(ZBuffer *zb,
/* Variant DT0_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -430,22 +446,24 @@ void ZB_fillTriangleSmooth_DT0_DW1(ZBuffer *zb,
#define INTERP_Z
#define INTERP_RGB
+#define INTERP_A
#define DRAW_INIT() \
{ \
}
-#define PUT_PIXEL(_a) \
- { \
- register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
- if (1 STIPTEST(_a)) { \
- TGL_BLEND_FUNC_RGB(or1, og1, ob1, (pp[_a])); \
- pz[_a] = zz; \
- } \
- z += dzdx; \
- og1 += dgdx; \
- or1 += drdx; \
- ob1 += dbdx; \
+#define PUT_PIXEL(_a) \
+ { \
+ register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
+ if (1 STIPTEST(_a)) { \
+ TGL_BLEND_FUNC_RGB(or1, og1, ob1, oa1, (pp[_a])); \
+ pz[_a] = zz; \
+ } \
+ z += dzdx; \
+ og1 += dgdx; \
+ or1 += drdx; \
+ ob1 += dbdx; \
+ oa1 += dadx; \
}
#include "ztriangle.h"
@@ -454,6 +472,7 @@ void ZB_fillTriangleSmooth_DT0_DW1(ZBuffer *zb,
/* Variant DT1_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -470,21 +489,23 @@ void ZB_fillTriangleSmooth_DT1_DW0(ZBuffer *zb,
#define INTERP_Z
#define INTERP_RGB
+#define INTERP_A
#define DRAW_INIT() \
{ \
}
-#define PUT_PIXEL(_a) \
- { \
- register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
- if ((zz >= pz[_a]) STIPTEST(_a)) { \
- TGL_BLEND_FUNC_RGB(or1, og1, ob1, (pp[_a])); \
- } \
- z += dzdx; \
- og1 += dgdx; \
- or1 += drdx; \
- ob1 += dbdx; \
+#define PUT_PIXEL(_a) \
+ { \
+ register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
+ if ((zz >= pz[_a]) STIPTEST(_a)) { \
+ TGL_BLEND_FUNC_RGB(or1, og1, ob1, oa1, (pp[_a])); \
+ } \
+ z += dzdx; \
+ og1 += dgdx; \
+ or1 += drdx; \
+ ob1 += dbdx; \
+ oa1 += dadx; \
}
#include "ztriangle.h"
@@ -493,6 +514,7 @@ void ZB_fillTriangleSmooth_DT1_DW0(ZBuffer *zb,
/* Variant DT1_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -509,22 +531,24 @@ void ZB_fillTriangleSmooth_DT1_DW1(ZBuffer *zb,
#define INTERP_Z
#define INTERP_RGB
+#define INTERP_A
#define DRAW_INIT() \
{ \
}
-#define PUT_PIXEL(_a) \
- { \
- register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
- if ((zz >= pz[_a]) STIPTEST(_a)) { \
- TGL_BLEND_FUNC_RGB(or1, og1, ob1, (pp[_a])); \
- pz[_a] = zz; \
- } \
- z += dzdx; \
- og1 += dgdx; \
- or1 += drdx; \
- ob1 += dbdx; \
+#define PUT_PIXEL(_a) \
+ { \
+ register GLuint zz = z >> ZB_POINT_Z_FRAC_BITS; \
+ if ((zz >= pz[_a]) STIPTEST(_a)) { \
+ TGL_BLEND_FUNC_RGB(or1, og1, ob1, oa1, (pp[_a])); \
+ pz[_a] = zz; \
+ } \
+ z += dzdx; \
+ og1 += dgdx; \
+ or1 += drdx; \
+ ob1 += dbdx; \
+ oa1 += dadx; \
}
#include "ztriangle.h"
@@ -539,6 +563,7 @@ void ZB_fillTriangleSmooth_DT1_DW1(ZBuffer *zb,
/* Variant DT0_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -577,6 +602,7 @@ void ZB_fillTriangleSmoothNOBLEND_DT0_DW0(ZBuffer *zb,
/* Variant DT0_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -616,6 +642,7 @@ void ZB_fillTriangleSmoothNOBLEND_DT0_DW1(ZBuffer *zb,
/* Variant DT1_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -654,6 +681,7 @@ void ZB_fillTriangleSmoothNOBLEND_DT1_DW0(ZBuffer *zb,
/* Variant DT1_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -790,6 +818,7 @@ void ZB_fillTriangleSmoothNOBLEND_DT1_DW1(ZBuffer *zb,
/* Variant DT0_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -844,6 +873,7 @@ void ZB_fillTriangleMappingPerspective_DT0_DW0(ZBuffer *zb,
/* Variant DT0_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -899,6 +929,7 @@ void ZB_fillTriangleMappingPerspective_DT0_DW1(ZBuffer *zb,
/* Variant DT1_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -953,6 +984,7 @@ void ZB_fillTriangleMappingPerspective_DT1_DW0(ZBuffer *zb,
/* Variant DT1_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -1014,6 +1046,7 @@ void ZB_fillTriangleMappingPerspective_DT1_DW1(ZBuffer *zb,
/* Variant DT0_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -1066,6 +1099,7 @@ void ZB_fillTriangleMappingPerspectiveNOBLEND_DT0_DW0(ZBuffer *zb,
/* Variant DT0_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -1119,6 +1153,7 @@ void ZB_fillTriangleMappingPerspectiveNOBLEND_DT0_DW1(ZBuffer *zb,
/* Variant DT1_DW0 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
@@ -1171,6 +1206,7 @@ void ZB_fillTriangleMappingPerspectiveNOBLEND_DT1_DW0(ZBuffer *zb,
/* Variant DT1_DW1 */
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
#undef DRAW_INIT
diff --git a/src/ztriangle.h b/src/ztriangle.h
index 271ce8f..64155b0 100644
--- a/src/ztriangle.h
+++ b/src/ztriangle.h
@@ -39,6 +39,9 @@
GLint g1, dgdx, dgdy, dgdl_min, dgdl_max;
GLint b1, dbdx, dbdy, dbdl_min, dbdl_max;
#endif
+#ifdef INTERP_A
+ GLint a1, dadx, dady, dadl_min, dadl_max;
+#endif
#ifdef INTERP_ST
GLint s1, dsdx, dsdy, dsdl_min, dsdl_max;
GLint t1, dtdx, dtdy, dtdl_min, dtdl_max;
@@ -119,6 +122,15 @@
}
#endif
+#ifdef INTERP_A
+ {
+ d1 = p1->a - p0->a;
+ d2 = p2->a - p0->a;
+ dadx = (GLint) (fdy2 * d1 - fdy1 * d2);
+ dady = (GLint) (fdx1 * d2 - fdx2 * d1);
+ }
+#endif
+
#ifdef INTERP_ST
{
d1 = p1->s - p0->s;
@@ -248,6 +260,11 @@
dbdl_min = (dbdy + dbdx * dxdy_min);
dbdl_max = dbdl_min + dbdx;
#endif
+#ifdef INTERP_A
+ a1 = l1->a;
+ dadl_min = (dady + dadx * dxdy_min);
+ dadl_max = dadl_min + dadx;
+#endif
#ifdef INTERP_ST
s1 = l1->s;
dsdl_min = (dsdy + dsdx * dxdy_min);
@@ -293,6 +310,9 @@
#ifdef INTERP_RGB
register GLint or1, og1, ob1;
#endif
+#ifdef INTERP_A
+ register GLint oa1;
+#endif
#ifdef INTERP_ST
register GLuint s, t;
#endif
@@ -312,6 +332,9 @@
og1 = g1;
ob1 = b1;
#endif
+#ifdef INTERP_A
+ oa1 = a1;
+#endif
#ifdef INTERP_ST
s = s1;
t = t1;
@@ -359,6 +382,9 @@
g1 += dgdl_max;
b1 += dbdl_max;
#endif
+#ifdef INTERP_A
+ a1 += dadl_max;
+#endif
#ifdef INTERP_ST
s1 += dsdl_max;
t1 += dtdl_max;
@@ -377,6 +403,9 @@
g1 += dgdl_min;
b1 += dbdl_min;
#endif
+#ifdef INTERP_A
+ a1 += dadl_min;
+#endif
#ifdef INTERP_ST
s1 += dsdl_min;
t1 += dtdl_min;
@@ -403,6 +432,7 @@
#undef INTERP_Z
#undef INTERP_RGB
+#undef INTERP_A
#undef INTERP_ST
#undef INTERP_STZ
diff --git a/tests/driver.sh b/tests/driver.sh
index a39388b..2dee214 100755
--- a/tests/driver.sh
+++ b/tests/driver.sh
@@ -600,6 +600,354 @@ int main(void) {
echo ""
fi
+# Alpha blending tests
+if [ "$RUN_API" -eq 1 ]; then
+echo "--- Alpha Blending Tests ---"
+
+# Common header for alpha tests - includes direct framebuffer access
+ALPHA_HEADER='
+#include
+#include
+#include
+#include
+#include
+#include "zbuffer.h"
+
+static ZBuffer *zb;
+static void setup(void) {
+ zb = ZB_open(64, 64, ZB_MODE_RGBA, NULL);
+ if (!zb) exit(1);
+ glInit(zb);
+ glViewport(0, 0, 64, 64);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glDisable(GL_LIGHTING);
+ glDisable(GL_DEPTH_TEST);
+ glShadeModel(GL_FLAT);
+}
+static void teardown(void) {
+ ZB_close(zb);
+ glClose();
+}
+/* Read pixel at (x,y) from framebuffer. Returns 0xAARRGGBB. */
+static unsigned int read_pixel(int x, int y) {
+ return ((unsigned int *)zb->pbuf)[y * zb->xsize + x];
+}
+/* Check if two color channel values are close enough (tolerance for rounding) */
+static int channel_close(int a, int b, int tol) {
+ int diff = a - b;
+ if (diff < 0) diff = -diff;
+ return diff <= tol;
+}
+'
+
+# Test: alpha=0 should be fully transparent (untextured)
+run_test "alpha_zero_transparent" "$ALPHA_HEADER
+int main(void) {
+ unsigned int pix;
+ setup();
+
+ /* Red background */
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ /* Draw green triangle with alpha=0 (should be invisible) */
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0.0f, 1.0f, 0.0f, 0.0f);
+ glBegin(GL_TRIANGLES);
+ glVertex3f(-0.8f, -0.8f, 0.0f);
+ glVertex3f( 0.8f, -0.8f, 0.0f);
+ glVertex3f( 0.0f, 0.8f, 0.0f);
+ glEnd();
+
+ /* Center pixel should still be red background */
+ pix = read_pixel(32, 32);
+ /* Check red=255, green=0, blue=0 */
+ if (((pix >> 16) & 0xFF) < 250 || ((pix >> 8) & 0xFF) > 5 || (pix & 0xFF) > 5) {
+ fprintf(stderr, \"alpha=0 not transparent: pixel=0x%08X\\n\", pix);
+ teardown();
+ return 1;
+ }
+
+ teardown();
+ return 0;
+}"
+
+# Test: alpha=255 should be fully opaque (untextured)
+run_test "alpha_full_opaque" "$ALPHA_HEADER
+int main(void) {
+ unsigned int pix;
+ setup();
+
+ /* Red background */
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ /* Draw green triangle with alpha=1.0 (fully opaque) */
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
+ glBegin(GL_TRIANGLES);
+ glVertex3f(-0.8f, -0.8f, 0.0f);
+ glVertex3f( 0.8f, -0.8f, 0.0f);
+ glVertex3f( 0.0f, 0.8f, 0.0f);
+ glEnd();
+
+ /* Center pixel should be green */
+ pix = read_pixel(32, 32);
+ if (((pix >> 16) & 0xFF) > 5 || ((pix >> 8) & 0xFF) < 250 || (pix & 0xFF) > 5) {
+ fprintf(stderr, \"alpha=1 not opaque: pixel=0x%08X\\n\", pix);
+ teardown();
+ return 1;
+ }
+
+ teardown();
+ return 0;
+}"
+
+# Test: alpha=0.5 should blend 50/50 (untextured)
+run_test "alpha_half_blend" "$ALPHA_HEADER
+int main(void) {
+ unsigned int pix;
+ int r, g;
+ setup();
+
+ /* Red background */
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ /* Draw green triangle with alpha=0.5 */
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0.0f, 1.0f, 0.0f, 0.5f);
+ glBegin(GL_TRIANGLES);
+ glVertex3f(-0.8f, -0.8f, 0.0f);
+ glVertex3f( 0.8f, -0.8f, 0.0f);
+ glVertex3f( 0.0f, 0.8f, 0.0f);
+ glEnd();
+
+ /* Center pixel should be ~(127,127,0) - blend of red and green */
+ pix = read_pixel(32, 32);
+ r = (pix >> 16) & 0xFF;
+ g = (pix >> 8) & 0xFF;
+ /* Both red and green should be roughly half intensity */
+ if (!channel_close(r, 127, 10) || !channel_close(g, 127, 10)) {
+ fprintf(stderr, \"alpha=0.5 blend wrong: pixel=0x%08X (r=%d g=%d)\\n\", pix, r, g);
+ teardown();
+ return 1;
+ }
+
+ teardown();
+ return 0;
+}"
+
+# Test: smooth shading with alpha interpolation
+run_test "alpha_smooth_interp" "$ALPHA_HEADER
+int main(void) {
+ unsigned int pix;
+ int r;
+ setup();
+
+ /* Red background */
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glShadeModel(GL_SMOOTH);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ /* Triangle: bottom-left fully opaque blue, bottom-right fully opaque blue,
+ top fully transparent blue. Center should have partial transparency. */
+ glBegin(GL_TRIANGLES);
+ glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
+ glVertex3f(-0.8f, -0.8f, 0.0f);
+ glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
+ glVertex3f( 0.8f, -0.8f, 0.0f);
+ glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
+ glVertex3f( 0.0f, 0.8f, 0.0f);
+ glEnd();
+
+ /* Center pixel should have some red bleeding through (partial alpha) */
+ pix = read_pixel(32, 32);
+ r = (pix >> 16) & 0xFF;
+ /* Red should be non-zero (background shows through) but not full red */
+ if (r < 20 || r > 235) {
+ fprintf(stderr, \"smooth alpha interp failed: pixel=0x%08X (r=%d)\\n\", pix, r);
+ teardown();
+ return 1;
+ }
+
+ teardown();
+ return 0;
+}"
+
+# Test: RGBA texture alpha=0 should be transparent
+run_test "alpha_texture_transparent" "$ALPHA_HEADER
+#define TEX_SIZE 256
+int main(void) {
+ static unsigned char rgba[TEX_SIZE * TEX_SIZE * 4];
+ unsigned int pix;
+ GLuint tex;
+ int i;
+ setup();
+
+ /* Fill texture: green with alpha=0 (fully transparent) */
+ for (i = 0; i < TEX_SIZE * TEX_SIZE; i++) {
+ rgba[i*4+0] = 0; /* R */
+ rgba[i*4+1] = 255; /* G */
+ rgba[i*4+2] = 0; /* B */
+ rgba[i*4+3] = 0; /* A = fully transparent */
+ }
+
+ /* Red background */
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, 4, TEX_SIZE, TEX_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ glEnable(GL_TEXTURE_2D);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ glBegin(GL_TRIANGLES);
+ glTexCoord2f(0, 0); glVertex3f(-0.8f, -0.8f, 0.0f);
+ glTexCoord2f(1, 0); glVertex3f( 0.8f, -0.8f, 0.0f);
+ glTexCoord2f(0.5f, 1); glVertex3f( 0.0f, 0.8f, 0.0f);
+ glEnd();
+
+ /* Center pixel should still be red (texture is fully transparent) */
+ pix = read_pixel(32, 32);
+ if (((pix >> 16) & 0xFF) < 250 || ((pix >> 8) & 0xFF) > 5 || (pix & 0xFF) > 5) {
+ fprintf(stderr, \"RGBA tex alpha=0 not transparent: pixel=0x%08X\\n\", pix);
+ glDeleteTextures(1, &tex);
+ teardown();
+ return 1;
+ }
+
+ glDeleteTextures(1, &tex);
+ teardown();
+ return 0;
+}"
+
+# Test: RGBA texture alpha=255 should be fully opaque
+run_test "alpha_texture_opaque" "$ALPHA_HEADER
+#define TEX_SIZE 256
+int main(void) {
+ static unsigned char rgba[TEX_SIZE * TEX_SIZE * 4];
+ unsigned int pix;
+ GLuint tex;
+ int i;
+ setup();
+
+ /* Fill texture: blue with alpha=255 (fully opaque) */
+ for (i = 0; i < TEX_SIZE * TEX_SIZE; i++) {
+ rgba[i*4+0] = 0; /* R */
+ rgba[i*4+1] = 0; /* G */
+ rgba[i*4+2] = 255; /* B */
+ rgba[i*4+3] = 255; /* A = fully opaque */
+ }
+
+ /* Red background */
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, 4, TEX_SIZE, TEX_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ glEnable(GL_TEXTURE_2D);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ glBegin(GL_TRIANGLES);
+ glTexCoord2f(0, 0); glVertex3f(-0.8f, -0.8f, 0.0f);
+ glTexCoord2f(1, 0); glVertex3f( 0.8f, -0.8f, 0.0f);
+ glTexCoord2f(0.5f, 1); glVertex3f( 0.0f, 0.8f, 0.0f);
+ glEnd();
+
+ /* Center pixel should be blue (texture is fully opaque) */
+ pix = read_pixel(32, 32);
+ if (((pix >> 16) & 0xFF) > 5 || ((pix >> 8) & 0xFF) > 5 || (pix & 0xFF) < 250) {
+ fprintf(stderr, \"RGBA tex alpha=255 not opaque: pixel=0x%08X\\n\", pix);
+ glDeleteTextures(1, &tex);
+ teardown();
+ return 1;
+ }
+
+ glDeleteTextures(1, &tex);
+ teardown();
+ return 0;
+}"
+
+# Test: RGBA texture with partial alpha should blend
+run_test "alpha_texture_half_blend" "$ALPHA_HEADER
+#define TEX_SIZE 256
+int main(void) {
+ static unsigned char rgba[TEX_SIZE * TEX_SIZE * 4];
+ unsigned int pix;
+ GLuint tex;
+ int i, r, b;
+ setup();
+
+ /* Fill texture: blue with alpha=128 (half transparent) */
+ for (i = 0; i < TEX_SIZE * TEX_SIZE; i++) {
+ rgba[i*4+0] = 0; /* R */
+ rgba[i*4+1] = 0; /* G */
+ rgba[i*4+2] = 255; /* B */
+ rgba[i*4+3] = 128; /* A = half */
+ }
+
+ /* Red background */
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, 4, TEX_SIZE, TEX_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ glEnable(GL_TEXTURE_2D);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ glBegin(GL_TRIANGLES);
+ glTexCoord2f(0, 0); glVertex3f(-0.8f, -0.8f, 0.0f);
+ glTexCoord2f(1, 0); glVertex3f( 0.8f, -0.8f, 0.0f);
+ glTexCoord2f(0.5f, 1); glVertex3f( 0.0f, 0.8f, 0.0f);
+ glEnd();
+
+ /* Center pixel should be ~(127, 0, 127) - blend of red bg and blue tex */
+ pix = read_pixel(32, 32);
+ r = (pix >> 16) & 0xFF;
+ b = pix & 0xFF;
+ if (!channel_close(r, 127, 10) || !channel_close(b, 127, 10)) {
+ fprintf(stderr, \"RGBA tex alpha=0.5 blend wrong: pixel=0x%08X (r=%d b=%d)\\n\", pix, r, b);
+ glDeleteTextures(1, &tex);
+ teardown();
+ return 1;
+ }
+
+ glDeleteTextures(1, &tex);
+ teardown();
+ return 0;
+}"
+
+echo ""
+fi
+
# Regression tests
if [ "$RUN_REGRESSION" -eq 1 ]; then
echo "--- Regression Tests ---"