/* * zazgl.c: the OpenGL attribute stacks that FTGL's texture fonts use (glPushAttrib, glPopAttrib, * glPushClientAttrib, glPopClientAttrib), for the fredygames.com browser build of Zaz. WebGL and * Emscripten's legacy GL emulation have none of them; FTGL only uses them to protect GL_TEXTURE_2D, * GL_BLEND, the blending function and the unpack alignment, so that is what is saved here. * Copyright (C) 2026 FredyGames. GNU GPL version 3 or later, like Zaz. */ #include #include #include #define ZAZ_STACK 8 #ifndef GL_BLEND_SRC_RGB #define GL_BLEND_SRC_RGB 0x80C9 #endif #ifndef GL_BLEND_DST_RGB #define GL_BLEND_DST_RGB 0x80C8 #endif static struct { GLboolean blend, texture; GLint src, dst; } attribs[ZAZ_STACK]; static int attrib_depth = 0; static GLint alignments[ZAZ_STACK]; static int client_depth = 0; void glPushAttrib (GLbitfield mask) { (void) mask; if (attrib_depth < ZAZ_STACK) { attribs[attrib_depth].blend = glIsEnabled (GL_BLEND); attribs[attrib_depth].texture = glIsEnabled (GL_TEXTURE_2D); /* WebGL only knows the separate RGB/alpha queries */ glGetIntegerv (GL_BLEND_SRC_RGB, &attribs[attrib_depth].src); glGetIntegerv (GL_BLEND_DST_RGB, &attribs[attrib_depth].dst); } attrib_depth++; } void glPopAttrib (void) { if (attrib_depth == 0) return; attrib_depth--; if (attrib_depth < ZAZ_STACK) { if (attribs[attrib_depth].blend) glEnable (GL_BLEND); else glDisable (GL_BLEND); if (attribs[attrib_depth].texture) glEnable (GL_TEXTURE_2D); else glDisable (GL_TEXTURE_2D); glBlendFunc ((GLenum) attribs[attrib_depth].src, (GLenum) attribs[attrib_depth].dst); } } void glPushClientAttrib (GLbitfield mask) { (void) mask; if (client_depth < ZAZ_STACK) glGetIntegerv (GL_UNPACK_ALIGNMENT, &alignments[client_depth]); client_depth++; } void glPopClientAttrib (void) { if (client_depth == 0) return; client_depth--; if (client_depth < ZAZ_STACK) glPixelStorei (GL_UNPACK_ALIGNMENT, alignments[client_depth]); } #ifndef GL_BGR #define GL_BGR 0x80E0 #endif #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif #ifndef GL_CLAMP #define GL_CLAMP 0x2900 #endif void zaz_TexParameteri (GLenum target, GLenum pname, GLint param) { if ((pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) && param == GL_CLAMP) param = GL_CLAMP_TO_EDGE; glTexParameteri (target, pname, param); } /* Textures uploaded as GL_ALPHA (FTGL's glyph atlases) become white GL_LUMINANCE_ALPHA ones: the * emulation samples GL_ALPHA as black, and with GL_MODULATE white keeps the text in the current color. * Since GL_REPLACE would then paint them white (in real OpenGL an alpha texture takes the color from * the vertex), such textures are drawn with GL_MODULATE whatever mode the game asked for. */ #define ZAZ_TEXTURES 4096 static unsigned char alpha_texture[ZAZ_TEXTURES]; static GLint requested_env = GL_MODULATE; static GLuint bound_texture = 0; static void apply_env (void) { GLint mode = requested_env; if (bound_texture < ZAZ_TEXTURES && alpha_texture[bound_texture] && mode == GL_REPLACE) mode = GL_MODULATE; glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode); } void zaz_TexEnvi (GLenum target, GLenum pname, GLint param) { if (target == GL_TEXTURE_ENV && pname == GL_TEXTURE_ENV_MODE) { requested_env = param; apply_env (); return; } glTexEnvi (target, pname, param); } void zaz_BindTexture (GLenum target, GLuint texture) { glBindTexture (target, texture); if (target == GL_TEXTURE_2D) { bound_texture = texture; apply_env (); } } /* GL_ALPHA rows (unpack alignment 1, as FTGL sets it) to white GL_LUMINANCE_ALPHA */ static unsigned char * alpha_to_luminance_alpha (const unsigned char *alpha, GLsizei width, GLsizei height) { size_t n = (size_t) width * height, i; unsigned char *la = (unsigned char *) malloc (n * 2 > 0 ? n * 2 : 1); for (i = 0; i < n; i++) { la[2 * i] = 255; la[2 * i + 1] = alpha != NULL ? alpha[i] : 0; } return la; } void zaz_TexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) { if (format == GL_ALPHA && type == GL_UNSIGNED_BYTE) { unsigned char *la = alpha_to_luminance_alpha ((const unsigned char *) pixels, width, height); glTexSubImage2D (target, level, xoffset, yoffset, width, height, GL_LUMINANCE_ALPHA, type, la); free (la); return; } glTexSubImage2D (target, level, xoffset, yoffset, width, height, format, type, pixels); } void zaz_TexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) { unsigned char *swapped = NULL; int bpp = 0; if (format == GL_ALPHA && type == GL_UNSIGNED_BYTE) { unsigned char *la = alpha_to_luminance_alpha ((const unsigned char *) pixels, width, height); GLint align; glGetIntegerv (GL_UNPACK_ALIGNMENT, &align); glPixelStorei (GL_UNPACK_ALIGNMENT, 1); glTexImage2D (target, level, GL_LUMINANCE_ALPHA, width, height, border, GL_LUMINANCE_ALPHA, type, la); glPixelStorei (GL_UNPACK_ALIGNMENT, align); free (la); if (bound_texture < ZAZ_TEXTURES) { alpha_texture[bound_texture] = 1; apply_env (); } return; } if (bound_texture < ZAZ_TEXTURES) alpha_texture[bound_texture] = 0; /* WebGL wants the internal format equal to the format, and knows no BGR order */ if (format == GL_BGR || format == GL_BGRA) { bpp = format == GL_BGR ? 3 : 4; format = format == GL_BGR ? GL_RGB : GL_RGBA; if (pixels != NULL) { /* rows are padded to 4 bytes, the unpack alignment the game leaves in place */ size_t pitch = ((size_t) width * bpp + 3) & ~(size_t) 3; size_t y, x; swapped = (unsigned char *) malloc (pitch * height); memcpy (swapped, pixels, pitch * height); for (y = 0; y < (size_t) height; y++) for (x = 0; x < (size_t) width; x++) { unsigned char *p = swapped + y * pitch + x * bpp; unsigned char t = p[0]; p[0] = p[2]; p[2] = t; } pixels = swapped; } } internalformat = (GLint) format; glTexImage2D (target, level, internalformat, width, height, border, format, type, pixels); free (swapped); }