From f127d98fe13c062d53dde384b3174f397a521cff Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Tue, 19 May 2026 19:06:58 -0700 Subject: [PATCH 1/6] feat: add alpha channel blending support - Add 'a' field to ZBufferPoint (8.16 fixed-point, same as r,g,b) - Propagate vertex alpha from glColor4f through the vertex pipeline - Add GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA blend factors to TGL_BLEND_FUNC and TGL_BLEND_FUNC_RGB macros - Use TGL_ALPHA_MUL helper with (factor + factor>>7) correction to map [0,255] -> [0,256], ensuring alpha=255 is identity and alpha=0 produces exactly zero (no darkening artifacts) - Preserve texture alpha through RGB_MIX_FUNC in 32-bit mode - Add INTERP_A support to ztriangle.h for per-vertex alpha interpolation - Update smooth-shaded blend triangles to interpolate and use alpha - Update flat-shaded blend triangles to pack vertex alpha into pixel - Update line/point blend paths to pass alpha --- include/zbuffer.h | 50 +++++++++++++--- src/api.d | 3 + src/arrays.d | 3 + src/clear.d | 2 + src/clip.d | 3 + src/get.d | 2 + src/image_util.d | 2 + src/init.d | 3 + src/light.d | 3 + src/list.d | 3 + src/matrix.d | 2 + src/memory.d | 2 + src/misc.d | 3 + src/msghandling.d | 3 + src/select.d | 3 + src/specbuf.d | 2 + src/texture.d | 3 + src/vertex.c | 5 ++ src/vertex.d | 2 + src/zbuffer.d | 2 + src/zline.c | 4 +- src/zline.d | 2 + src/zmath.d | 1 + src/zpostprocess.d | 2 + src/zraster.d | 3 + src/ztext.d | 3 + src/ztriangle.c | 144 ++++++++++++++++++++++++++++----------------- src/ztriangle.d | 3 + src/ztriangle.h | 30 ++++++++++ 29 files changed, 230 insertions(+), 63 deletions(-) create mode 100644 src/api.d create mode 100644 src/arrays.d create mode 100644 src/clear.d create mode 100644 src/clip.d create mode 100644 src/get.d create mode 100644 src/image_util.d create mode 100644 src/init.d create mode 100644 src/light.d create mode 100644 src/list.d create mode 100644 src/matrix.d create mode 100644 src/memory.d create mode 100644 src/misc.d create mode 100644 src/msghandling.d create mode 100644 src/select.d create mode 100644 src/specbuf.d create mode 100644 src/texture.d create mode 100644 src/vertex.d create mode 100644 src/zbuffer.d create mode 100644 src/zline.d create mode 100644 src/zmath.d create mode 100644 src/zpostprocess.d create mode 100644 src/zraster.d create mode 100644 src/ztext.d create mode 100644 src/ztriangle.d 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/api.d b/src/api.d new file mode 100644 index 0000000..1b4b68b --- /dev/null +++ b/src/api.d @@ -0,0 +1,3 @@ +src/api.o: src/api.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check_no_context.h src/error_check.h diff --git a/src/arrays.d b/src/arrays.d new file mode 100644 index 0000000..8679727 --- /dev/null +++ b/src/arrays.d @@ -0,0 +1,3 @@ +src/arrays.o: src/arrays.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check.h src/error_check_no_context.h diff --git a/src/clear.d b/src/clear.d new file mode 100644 index 0000000..33771e3 --- /dev/null +++ b/src/clear.d @@ -0,0 +1,2 @@ +src/clear.o: src/clear.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/clip.d b/src/clip.d new file mode 100644 index 0000000..c56ddbc --- /dev/null +++ b/src/clip.d @@ -0,0 +1,3 @@ +src/clip.o: src/clip.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ + src/ztriangle_variants.h diff --git a/src/get.d b/src/get.d new file mode 100644 index 0000000..ad7f85a --- /dev/null +++ b/src/get.d @@ -0,0 +1,2 @@ +src/get.o: src/get.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/image_util.d b/src/image_util.d new file mode 100644 index 0000000..c81db38 --- /dev/null +++ b/src/image_util.d @@ -0,0 +1,2 @@ +src/image_util.o: src/image_util.c src/zgl.h include/TGL/gl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/init.d b/src/init.d new file mode 100644 index 0000000..8f43861 --- /dev/null +++ b/src/init.d @@ -0,0 +1,3 @@ +src/init.o: src/init.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check.h diff --git a/src/light.d b/src/light.d new file mode 100644 index 0000000..bd6dbe9 --- /dev/null +++ b/src/light.d @@ -0,0 +1,3 @@ +src/light.o: src/light.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check_no_context.h diff --git a/src/list.d b/src/list.d new file mode 100644 index 0000000..b11f000 --- /dev/null +++ b/src/list.d @@ -0,0 +1,3 @@ +src/list.o: src/list.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check_no_context.h src/error_check.h diff --git a/src/matrix.d b/src/matrix.d new file mode 100644 index 0000000..3199b5c --- /dev/null +++ b/src/matrix.d @@ -0,0 +1,2 @@ +src/matrix.o: src/matrix.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/memory.d b/src/memory.d new file mode 100644 index 0000000..26b8e5a --- /dev/null +++ b/src/memory.d @@ -0,0 +1,2 @@ +src/memory.o: src/memory.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/misc.d b/src/misc.d new file mode 100644 index 0000000..04f325e --- /dev/null +++ b/src/misc.d @@ -0,0 +1,3 @@ +src/misc.o: src/misc.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check_no_context.h src/error_check.h diff --git a/src/msghandling.d b/src/msghandling.d new file mode 100644 index 0000000..4ec2814 --- /dev/null +++ b/src/msghandling.d @@ -0,0 +1,3 @@ +src/msghandling.o: src/msghandling.c include/TGL/gl.h src/msghandling.h \ + src/zgl.h include/zbuffer.h include/zfeatures.h src/zmath.h \ + src/opinfo.h diff --git a/src/select.d b/src/select.d new file mode 100644 index 0000000..616eeb5 --- /dev/null +++ b/src/select.d @@ -0,0 +1,3 @@ +src/select.o: src/select.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check_no_context.h diff --git a/src/specbuf.d b/src/specbuf.d new file mode 100644 index 0000000..e987544 --- /dev/null +++ b/src/specbuf.d @@ -0,0 +1,2 @@ +src/specbuf.o: src/specbuf.c src/msghandling.h include/TGL/gl.h src/zgl.h \ + include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/texture.d b/src/texture.d new file mode 100644 index 0000000..0969e5c --- /dev/null +++ b/src/texture.d @@ -0,0 +1,3 @@ +src/texture.o: src/texture.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zmath.h src/opinfo.h \ + src/error_check_no_context.h src/error_check.h 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/vertex.d b/src/vertex.d new file mode 100644 index 0000000..27291ac --- /dev/null +++ b/src/vertex.d @@ -0,0 +1,2 @@ +src/vertex.o: src/vertex.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zmath.h src/opinfo.h src/error_check.h diff --git a/src/zbuffer.d b/src/zbuffer.d new file mode 100644 index 0000000..5d0d7ab --- /dev/null +++ b/src/zbuffer.d @@ -0,0 +1,2 @@ +src/zbuffer.o: src/zbuffer.c src/msghandling.h include/TGL/gl.h \ + include/zbuffer.h include/zfeatures.h 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/zline.d b/src/zline.d new file mode 100644 index 0000000..ee431ee --- /dev/null +++ b/src/zline.d @@ -0,0 +1,2 @@ +src/zline.o: src/zline.c include/zbuffer.h include/TGL/gl.h \ + include/zfeatures.h src/zline.h diff --git a/src/zmath.d b/src/zmath.d new file mode 100644 index 0000000..797ee74 --- /dev/null +++ b/src/zmath.d @@ -0,0 +1 @@ +src/zmath.o: src/zmath.c src/zmath.h include/TGL/gl.h include/zfeatures.h diff --git a/src/zpostprocess.d b/src/zpostprocess.d new file mode 100644 index 0000000..c1de17d --- /dev/null +++ b/src/zpostprocess.d @@ -0,0 +1,2 @@ +src/zpostprocess.o: src/zpostprocess.c include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zgl.h src/zmath.h src/opinfo.h diff --git a/src/zraster.d b/src/zraster.d new file mode 100644 index 0000000..a3761a0 --- /dev/null +++ b/src/zraster.d @@ -0,0 +1,3 @@ +src/zraster.o: src/zraster.c include/TGL/gl.h src/msghandling.h \ + include/zbuffer.h include/zfeatures.h src/zgl.h src/zmath.h \ + src/opinfo.h diff --git a/src/ztext.d b/src/ztext.d new file mode 100644 index 0000000..3faabe9 --- /dev/null +++ b/src/ztext.d @@ -0,0 +1,3 @@ +src/ztext.o: src/ztext.c include/TGL/gl.h include/zbuffer.h \ + include/zfeatures.h src/zgl.h src/zmath.h src/opinfo.h \ + src/font8x8_basic.h src/error_check_no_context.h src/error_check.h 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.d b/src/ztriangle.d new file mode 100644 index 0000000..01e6b10 --- /dev/null +++ b/src/ztriangle.d @@ -0,0 +1,3 @@ +src/ztriangle.o: src/ztriangle.c src/msghandling.h include/TGL/gl.h \ + include/zbuffer.h include/zfeatures.h src/ztriangle_variants.h \ + src/ztriangle.h 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 From 0b23916e63403c177de3f914e2f8dc0e4da488f3 Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Tue, 19 May 2026 19:18:20 -0700 Subject: [PATCH 2/6] feat: support GL_RGBA format in glTexImage2D - Add gl_convertRGBA_to_8A8R8G8B() that preserves the alpha channel when converting from RGBA byte data to internal 0xAARRGGBB format - Add gl_resizeImageNoInterpolate4() for resizing 4-channel textures - Update glopTexImage2D to accept GL_RGBA with components=4 - Texture alpha is preserved through TGL_BLEND_FUNC via the pixel's high byte, enabling proper alpha-blended textured geometry --- src/image_util.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ src/texture.c | 32 +++++++++++++++++++++-------- src/zgl.h | 10 +++++++++ 3 files changed, 86 insertions(+), 9 deletions(-) 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/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, ...); From 90fd7af567ee9fdc7023ea428b918671641cefbc Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Tue, 19 May 2026 20:27:35 -0700 Subject: [PATCH 3/6] test: add alpha blending verification tests Add 7 tests covering alpha channel rendering correctness: Untextured: - alpha_zero_transparent: alpha=0 produces fully transparent output - alpha_full_opaque: alpha=1 produces fully opaque output - alpha_half_blend: alpha=0.5 produces correct 50/50 blend - alpha_smooth_interp: GL_SMOOTH interpolates alpha per-vertex Textured (GL_RGBA): - alpha_texture_transparent: RGBA texture with alpha=0 is invisible - alpha_texture_opaque: RGBA texture with alpha=255 is fully visible - alpha_texture_half_blend: RGBA texture with alpha=128 blends correctly All tests use direct framebuffer readback and verify pixel values with a small tolerance for fixed-point rounding. --- tests/driver.sh | 348 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) 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 ---" From 06f2fb21e86a800fa8fecf909018c5b8dcd07eea Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Tue, 19 May 2026 20:28:21 -0700 Subject: [PATCH 4/6] chore: ignore .d dependency files --- .gitignore | 1 + src/api.d | 3 --- src/arrays.d | 3 --- src/clear.d | 2 -- src/clip.d | 3 --- src/get.d | 2 -- src/image_util.d | 2 -- src/init.d | 3 --- src/light.d | 3 --- src/list.d | 3 --- src/matrix.d | 2 -- src/memory.d | 2 -- src/misc.d | 3 --- src/msghandling.d | 3 --- src/select.d | 3 --- src/specbuf.d | 2 -- src/texture.d | 3 --- src/vertex.d | 2 -- src/zbuffer.d | 2 -- src/zline.d | 2 -- src/zmath.d | 1 - src/zpostprocess.d | 2 -- src/zraster.d | 3 --- src/ztext.d | 3 --- src/ztriangle.d | 3 --- 25 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 src/api.d delete mode 100644 src/arrays.d delete mode 100644 src/clear.d delete mode 100644 src/clip.d delete mode 100644 src/get.d delete mode 100644 src/image_util.d delete mode 100644 src/init.d delete mode 100644 src/light.d delete mode 100644 src/list.d delete mode 100644 src/matrix.d delete mode 100644 src/memory.d delete mode 100644 src/misc.d delete mode 100644 src/msghandling.d delete mode 100644 src/select.d delete mode 100644 src/specbuf.d delete mode 100644 src/texture.d delete mode 100644 src/vertex.d delete mode 100644 src/zbuffer.d delete mode 100644 src/zline.d delete mode 100644 src/zmath.d delete mode 100644 src/zpostprocess.d delete mode 100644 src/zraster.d delete mode 100644 src/ztext.d delete mode 100644 src/ztriangle.d 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/src/api.d b/src/api.d deleted file mode 100644 index 1b4b68b..0000000 --- a/src/api.d +++ /dev/null @@ -1,3 +0,0 @@ -src/api.o: src/api.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check_no_context.h src/error_check.h diff --git a/src/arrays.d b/src/arrays.d deleted file mode 100644 index 8679727..0000000 --- a/src/arrays.d +++ /dev/null @@ -1,3 +0,0 @@ -src/arrays.o: src/arrays.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check.h src/error_check_no_context.h diff --git a/src/clear.d b/src/clear.d deleted file mode 100644 index 33771e3..0000000 --- a/src/clear.d +++ /dev/null @@ -1,2 +0,0 @@ -src/clear.o: src/clear.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/clip.d b/src/clip.d deleted file mode 100644 index c56ddbc..0000000 --- a/src/clip.d +++ /dev/null @@ -1,3 +0,0 @@ -src/clip.o: src/clip.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ - src/ztriangle_variants.h diff --git a/src/get.d b/src/get.d deleted file mode 100644 index ad7f85a..0000000 --- a/src/get.d +++ /dev/null @@ -1,2 +0,0 @@ -src/get.o: src/get.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/image_util.d b/src/image_util.d deleted file mode 100644 index c81db38..0000000 --- a/src/image_util.d +++ /dev/null @@ -1,2 +0,0 @@ -src/image_util.o: src/image_util.c src/zgl.h include/TGL/gl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/init.d b/src/init.d deleted file mode 100644 index 8f43861..0000000 --- a/src/init.d +++ /dev/null @@ -1,3 +0,0 @@ -src/init.o: src/init.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check.h diff --git a/src/light.d b/src/light.d deleted file mode 100644 index bd6dbe9..0000000 --- a/src/light.d +++ /dev/null @@ -1,3 +0,0 @@ -src/light.o: src/light.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check_no_context.h diff --git a/src/list.d b/src/list.d deleted file mode 100644 index b11f000..0000000 --- a/src/list.d +++ /dev/null @@ -1,3 +0,0 @@ -src/list.o: src/list.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check_no_context.h src/error_check.h diff --git a/src/matrix.d b/src/matrix.d deleted file mode 100644 index 3199b5c..0000000 --- a/src/matrix.d +++ /dev/null @@ -1,2 +0,0 @@ -src/matrix.o: src/matrix.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/memory.d b/src/memory.d deleted file mode 100644 index 26b8e5a..0000000 --- a/src/memory.d +++ /dev/null @@ -1,2 +0,0 @@ -src/memory.o: src/memory.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/misc.d b/src/misc.d deleted file mode 100644 index 04f325e..0000000 --- a/src/misc.d +++ /dev/null @@ -1,3 +0,0 @@ -src/misc.o: src/misc.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check_no_context.h src/error_check.h diff --git a/src/msghandling.d b/src/msghandling.d deleted file mode 100644 index 4ec2814..0000000 --- a/src/msghandling.d +++ /dev/null @@ -1,3 +0,0 @@ -src/msghandling.o: src/msghandling.c include/TGL/gl.h src/msghandling.h \ - src/zgl.h include/zbuffer.h include/zfeatures.h src/zmath.h \ - src/opinfo.h diff --git a/src/select.d b/src/select.d deleted file mode 100644 index 616eeb5..0000000 --- a/src/select.d +++ /dev/null @@ -1,3 +0,0 @@ -src/select.o: src/select.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check_no_context.h diff --git a/src/specbuf.d b/src/specbuf.d deleted file mode 100644 index e987544..0000000 --- a/src/specbuf.d +++ /dev/null @@ -1,2 +0,0 @@ -src/specbuf.o: src/specbuf.c src/msghandling.h include/TGL/gl.h src/zgl.h \ - include/zbuffer.h include/zfeatures.h src/zmath.h src/opinfo.h diff --git a/src/texture.d b/src/texture.d deleted file mode 100644 index 0969e5c..0000000 --- a/src/texture.d +++ /dev/null @@ -1,3 +0,0 @@ -src/texture.o: src/texture.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zmath.h src/opinfo.h \ - src/error_check_no_context.h src/error_check.h diff --git a/src/vertex.d b/src/vertex.d deleted file mode 100644 index 27291ac..0000000 --- a/src/vertex.d +++ /dev/null @@ -1,2 +0,0 @@ -src/vertex.o: src/vertex.c src/zgl.h include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zmath.h src/opinfo.h src/error_check.h diff --git a/src/zbuffer.d b/src/zbuffer.d deleted file mode 100644 index 5d0d7ab..0000000 --- a/src/zbuffer.d +++ /dev/null @@ -1,2 +0,0 @@ -src/zbuffer.o: src/zbuffer.c src/msghandling.h include/TGL/gl.h \ - include/zbuffer.h include/zfeatures.h diff --git a/src/zline.d b/src/zline.d deleted file mode 100644 index ee431ee..0000000 --- a/src/zline.d +++ /dev/null @@ -1,2 +0,0 @@ -src/zline.o: src/zline.c include/zbuffer.h include/TGL/gl.h \ - include/zfeatures.h src/zline.h diff --git a/src/zmath.d b/src/zmath.d deleted file mode 100644 index 797ee74..0000000 --- a/src/zmath.d +++ /dev/null @@ -1 +0,0 @@ -src/zmath.o: src/zmath.c src/zmath.h include/TGL/gl.h include/zfeatures.h diff --git a/src/zpostprocess.d b/src/zpostprocess.d deleted file mode 100644 index c1de17d..0000000 --- a/src/zpostprocess.d +++ /dev/null @@ -1,2 +0,0 @@ -src/zpostprocess.o: src/zpostprocess.c include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zgl.h src/zmath.h src/opinfo.h diff --git a/src/zraster.d b/src/zraster.d deleted file mode 100644 index a3761a0..0000000 --- a/src/zraster.d +++ /dev/null @@ -1,3 +0,0 @@ -src/zraster.o: src/zraster.c include/TGL/gl.h src/msghandling.h \ - include/zbuffer.h include/zfeatures.h src/zgl.h src/zmath.h \ - src/opinfo.h diff --git a/src/ztext.d b/src/ztext.d deleted file mode 100644 index 3faabe9..0000000 --- a/src/ztext.d +++ /dev/null @@ -1,3 +0,0 @@ -src/ztext.o: src/ztext.c include/TGL/gl.h include/zbuffer.h \ - include/zfeatures.h src/zgl.h src/zmath.h src/opinfo.h \ - src/font8x8_basic.h src/error_check_no_context.h src/error_check.h diff --git a/src/ztriangle.d b/src/ztriangle.d deleted file mode 100644 index 01e6b10..0000000 --- a/src/ztriangle.d +++ /dev/null @@ -1,3 +0,0 @@ -src/ztriangle.o: src/ztriangle.c src/msghandling.h include/TGL/gl.h \ - include/zbuffer.h include/zfeatures.h src/ztriangle_variants.h \ - src/ztriangle.h From ba1f7385afdd780b6ec6f1c8fd9ae0e900dd6ac0 Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Wed, 20 May 2026 07:51:11 -0700 Subject: [PATCH 5/6] feat: Add example usage of the alpha channel --- examples/raw/README.md | 17 ++++ examples/raw/alpha.c | 200 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 examples/raw/README.md create mode 100644 examples/raw/alpha.c diff --git a/examples/raw/README.md b/examples/raw/README.md new file mode 100644 index 0000000..a01adf2 --- /dev/null +++ b/examples/raw/README.md @@ -0,0 +1,17 @@ +# 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. 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; +} From ff9f44c9201a079ba7db14776e86dff012fbaf4d Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Wed, 20 May 2026 07:52:38 -0700 Subject: [PATCH 6/6] doc: add screenshot --- examples/raw/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/raw/README.md b/examples/raw/README.md index a01adf2..a936943 100644 --- a/examples/raw/README.md +++ b/examples/raw/README.md @@ -14,4 +14,6 @@ To run this demo: 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. +Then find `alpha_demo.png` in your working directory. Expected image: + +alpha_demo