/* * cbgl: see cbgl.h. Vertices are transformed on the CPU by the current matrices (sphere-map texture * coordinates included) and drawn in clip space by one shader, which applies the two texture units * and the alpha test. Primitives WebGL lacks become triangles (GL_QUADS, GL_QUAD_STRIP, GL_POLYGON), * and display lists are recorded as a list of calls and replayed. * * Written in 2026 by FredyGames for the fredygames.com build of Chromium B.S.U.; distributed with the * game under the Clarified Artistic License (see COPYING). */ #define CBGL_IMPLEMENTATION #include "cbgl.h" #include #include #include #include /*===========================================================================*/ // Matrices (column-major, as in OpenGL): typedef struct { float m[16]; } mat4; #define STACK_DEPTH 32 static mat4 stacks[3][STACK_DEPTH]; // modelview, projection, texture (unit 0) static int tops[3] = {0, 0, 0}; static int mode_index = 0; static int initialized = 0; static void identity(mat4 *a) { memset(a, 0, sizeof(*a)); a->m[0] = a->m[5] = a->m[10] = a->m[15] = 1.0f; } static mat4 *current(void) { return &stacks[mode_index][tops[mode_index]]; } // *a = *a * b static void multiply(mat4 *a, const float *b) { mat4 r; for (int col = 0; col < 4; ++col) { for (int row = 0; row < 4; ++row) { float sum = 0.0f; for (int k = 0; k < 4; ++k) sum += a->m[k * 4 + row] * b[col * 4 + k]; r.m[col * 4 + row] = sum; } } *a = r; } static void init(void) { if (initialized) return; initialized = 1; for (int i = 0; i < 3; ++i) identity(&stacks[i][0]); } /*===========================================================================*/ // Display lists: enum { OP_BEGIN, OP_END, OP_VERTEX, OP_TEXCOORD, OP_COLOR, OP_NORMAL, OP_PUSH, OP_POP, OP_IDENTITY, OP_MODE, OP_TRANSLATE, OP_SCALE, OP_ROTATE, OP_CALL }; typedef struct { int op; float v[4]; } call_t; typedef struct { call_t *calls; int count, capacity; int defined; } list_t; static list_t *lists = NULL; static GLuint num_lists = 0; static list_t *recording = NULL; static int execute_while_recording = 0; static void record(int op, float a, float b, float c, float d) { if (recording->count == recording->capacity) { recording->capacity = recording->capacity ? recording->capacity * 2 : 256; recording->calls = realloc(recording->calls, recording->capacity * sizeof(call_t)); } call_t *call = &recording->calls[recording->count++]; call->op = op; call->v[0] = a; call->v[1] = b; call->v[2] = c; call->v[3] = d; } // Records the call when a list is being compiled; returns whether to also run it now. #define RECORD(op, a, b, c, d) \ do { if (recording != NULL) { record((op), (a), (b), (c), (d)); \ if (!execute_while_recording) return; } } while (0) GLuint cbgl_GenLists(GLsizei range) { const GLuint first = num_lists + 1; num_lists += range; lists = realloc(lists, (num_lists + 1) * sizeof(list_t)); memset(&lists[first], 0, range * sizeof(list_t)); return first; } void cbgl_DeleteLists(GLuint list, GLsizei range) { for (GLuint i = list; i < list + (GLuint)range && i <= num_lists; ++i) { if (i < 1) continue; free(lists[i].calls); memset(&lists[i], 0, sizeof(list_t)); } } void cbgl_NewList(GLuint list, GLenum mode) { if (list < 1 || list > num_lists) return; recording = &lists[list]; recording->count = 0; recording->defined = 1; execute_while_recording = (mode == GL_COMPILE_AND_EXECUTE); } void cbgl_EndList(void) { recording = NULL; } /*===========================================================================*/ // Matrix calls: void cbgl_MatrixMode(GLenum mode) { init(); RECORD(OP_MODE, mode, 0, 0, 0); mode_index = mode == GL_PROJECTION ? 1 : mode == GL_TEXTURE ? 2 : 0; } void cbgl_PushMatrix(void) { init(); RECORD(OP_PUSH, 0, 0, 0, 0); int *top = &tops[mode_index]; if (*top + 1 < STACK_DEPTH) { stacks[mode_index][*top + 1] = stacks[mode_index][*top]; ++*top; } } void cbgl_PopMatrix(void) { init(); RECORD(OP_POP, 0, 0, 0, 0); if (tops[mode_index] > 0) --tops[mode_index]; } void cbgl_LoadIdentity(void) { init(); RECORD(OP_IDENTITY, 0, 0, 0, 0); identity(current()); } void cbgl_Translatef(GLfloat x, GLfloat y, GLfloat z) { init(); RECORD(OP_TRANSLATE, x, y, z, 0); mat4 t; identity(&t); t.m[12] = x; t.m[13] = y; t.m[14] = z; multiply(current(), t.m); } void cbgl_Scalef(GLfloat x, GLfloat y, GLfloat z) { init(); RECORD(OP_SCALE, x, y, z, 0); mat4 s; identity(&s); s.m[0] = x; s.m[5] = y; s.m[10] = z; multiply(current(), s.m); } void cbgl_Rotatef(GLfloat angle, GLfloat fx, GLfloat fy, GLfloat fz) { init(); RECORD(OP_ROTATE, angle, fx, fy, fz); double x = fx, y = fy, z = fz; const double len = sqrt(x * x + y * y + z * z); if (len == 0.0) return; x /= len; y /= len; z /= len; const double rad = angle * 3.14159265358979323846 / 180.0, c = cos(rad), s = sin(rad), ic = 1.0 - c; mat4 r; identity(&r); r.m[0] = x * x * ic + c; r.m[4] = x * y * ic - z * s; r.m[8] = x * z * ic + y * s; r.m[1] = y * x * ic + z * s; r.m[5] = y * y * ic + c; r.m[9] = y * z * ic - x * s; r.m[2] = x * z * ic - y * s; r.m[6] = y * z * ic + x * s; r.m[10] = z * z * ic + c; multiply(current(), r.m); } void cbgl_Perspective(GLdouble fovy, GLdouble aspect, GLdouble znear, GLdouble zfar) { init(); const double f = 1.0 / tan(fovy * 3.14159265358979323846 / 360.0); mat4 p; memset(&p, 0, sizeof(p)); p.m[0] = f / aspect; p.m[5] = f; p.m[10] = (zfar + znear) / (znear - zfar); p.m[11] = -1.0f; p.m[14] = 2.0 * zfar * znear / (znear - zfar); multiply(current(), p.m); } /*===========================================================================*/ // Textures: WebGL 1 has no GL_CLAMP and no mipmaps or repeat on textures whose sides are not powers // of two, and a mipmap filter without mipmaps leaves the texture black. typedef struct { int width, height, min_filter; } texinfo_t; static texinfo_t *textures = NULL; static GLuint textures_capacity = 0; static int active_unit = 0; static GLuint bound[2] = {0, 0}; static texinfo_t *info(GLuint texture) { if (texture == 0) return NULL; if (texture >= textures_capacity) { GLuint capacity = textures_capacity ? textures_capacity : 64; while (capacity <= texture) capacity *= 2; textures = realloc(textures, capacity * sizeof(texinfo_t)); memset(&textures[textures_capacity], 0, (capacity - textures_capacity) * sizeof(texinfo_t)); textures_capacity = capacity; } return &textures[texture]; } static int power_of_two(int n) { return n > 0 && (n & (n - 1)) == 0; } static int is_mipmap_filter(GLint filter) { return filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_LINEAR_MIPMAP_NEAREST || filter == GL_NEAREST_MIPMAP_LINEAR || filter == GL_LINEAR_MIPMAP_LINEAR; } void cbgl_ActiveTexture(GLenum unit) { active_unit = unit == GL_TEXTURE1 ? 1 : 0; glActiveTexture(unit); } void cbgl_BindTexture(GLenum target, GLuint texture) { bound[active_unit] = texture; glBindTexture(target, texture); } void cbgl_DeleteTextures(GLsizei n, const GLuint *list) { for (GLsizei i = 0; i < n; ++i) { texinfo_t *t = info(list[i]); if (t != NULL) memset(t, 0, sizeof(*t)); for (int u = 0; u < 2; ++u) if (bound[u] == list[i]) bound[u] = 0; } glDeleteTextures(n, list); } void cbgl_TexParameteri(GLenum target, GLenum pname, GLint param) { texinfo_t *t = info(bound[active_unit]); const int npot = t != NULL && t->width > 0 && !(power_of_two(t->width) && power_of_two(t->height)); if (pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) { if (param == GL_CLAMP || npot) param = GL_CLAMP_TO_EDGE; } else if (pname == GL_TEXTURE_MIN_FILTER) { if (t != NULL) t->min_filter = param; if (npot && is_mipmap_filter(param)) param = GL_LINEAR; } glTexParameteri(target, pname, param); } static void upload(GLenum target, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels, int mipmaps) { // WebGL takes the internal format from the format; the game passes a component count. glTexImage2D(target, 0, format, width, height, 0, format, type, pixels); texinfo_t *t = info(bound[active_unit]); if (t == NULL) return; t->width = width; t->height = height; if (!(power_of_two(width) && power_of_two(height))) { glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); if (is_mipmap_filter(t->min_filter)) glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } else if (mipmaps || is_mipmap_filter(t->min_filter)) { glGenerateMipmap(target); } } void cbgl_TexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) { (void)internalformat; (void)border; if (level != 0) return; // mipmaps come from glGenerateMipmap upload(target, width, height, format, type, pixels, 0); } GLint cbgl_Build2DMipmaps(GLenum target, GLint internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data) { (void)internalformat; upload(target, width, height, format, type, data, 1); return 0; } /*===========================================================================*/ // Fixed-function state that WebGL lacks: static int texture_enabled[2] = {0, 0}; static GLenum texture_env[2] = {GL_MODULATE, GL_MODULATE}; static int texgen_s = 0, texgen_t = 0; static int alpha_test = 0; static GLenum alpha_func = GL_ALWAYS; static float alpha_ref = 0.0f; void cbgl_Enable(GLenum cap) { switch (cap) { case GL_TEXTURE_2D: texture_enabled[active_unit] = 1; break; case GL_TEXTURE_GEN_S: texgen_s = 1; break; case GL_TEXTURE_GEN_T: texgen_t = 1; break; case GL_ALPHA_TEST: alpha_test = 1; break; case GL_BLEND: case GL_CULL_FACE: case GL_DEPTH_TEST: case GL_SCISSOR_TEST: case GL_POLYGON_OFFSET_FILL: case GL_DITHER: glEnable(cap); break; default: break; // GL_NORMALIZE and the like change nothing here } } void cbgl_Disable(GLenum cap) { switch (cap) { case GL_TEXTURE_2D: texture_enabled[active_unit] = 0; break; case GL_TEXTURE_GEN_S: texgen_s = 0; break; case GL_TEXTURE_GEN_T: texgen_t = 0; break; case GL_ALPHA_TEST: alpha_test = 0; break; case GL_BLEND: case GL_CULL_FACE: case GL_DEPTH_TEST: case GL_SCISSOR_TEST: case GL_POLYGON_OFFSET_FILL: case GL_DITHER: glDisable(cap); break; default: break; } } void cbgl_AlphaFunc(GLenum func, GLclampf ref) { alpha_func = func; alpha_ref = ref; } void cbgl_TexEnvf(GLenum target, GLenum pname, GLfloat param) { if (target == GL_TEXTURE_ENV && pname == GL_TEXTURE_ENV_MODE) texture_env[active_unit] = (GLenum)param; } // The game only asks for GL_SPHERE_MAP. void cbgl_TexGeni(GLenum coord, GLenum pname, GLint param) { (void)coord; (void)pname; (void)param; } /*===========================================================================*/ // Drawing: typedef struct { float pos[4], color[4], uv0[2], uv1[2]; } vertex_t; static GLenum begin_mode = 0; static float color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; static float normal[3] = {0.0f, 0.0f, 1.0f}; static float texcoord[2][2] = {{0.0f, 0.0f}, {0.0f, 0.0f}}; static vertex_t *verts = NULL, *tris = NULL; static int num_verts = 0, verts_capacity = 0, num_tris = 0, tris_capacity = 0; static GLuint program = 0, buffer = 0; static GLint attr_pos, attr_color, attr_uv0, attr_uv1; static GLint u_tex0, u_tex1, u_env0, u_env1, u_alpha_func, u_alpha_ref; void cbgl_Color4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { RECORD(OP_COLOR, r, g, b, a); color[0] = r; color[1] = g; color[2] = b; color[3] = a; } void cbgl_Color4fv(const GLfloat *c) { cbgl_Color4f(c[0], c[1], c[2], c[3]); } void cbgl_Normal3f(GLfloat x, GLfloat y, GLfloat z) { RECORD(OP_NORMAL, x, y, z, 0); normal[0] = x; normal[1] = y; normal[2] = z; } void cbgl_Normal3fv(const GLfloat *n) { cbgl_Normal3f(n[0], n[1], n[2]); } void cbgl_MultiTexCoord2f(GLenum unit, GLfloat s, GLfloat t) { const int u = unit == GL_TEXTURE1 ? 1 : 0; RECORD(OP_TEXCOORD, u, s, t, 0); texcoord[u][0] = s; texcoord[u][1] = t; } void cbgl_TexCoord2f(GLfloat s, GLfloat t) { cbgl_MultiTexCoord2f(GL_TEXTURE0, s, t); } void cbgl_Begin(GLenum mode) { init(); RECORD(OP_BEGIN, mode, 0, 0, 0); begin_mode = mode; num_verts = 0; } void cbgl_Vertex3f(GLfloat x, GLfloat y, GLfloat z) { RECORD(OP_VERTEX, x, y, z, 0); if (num_verts == verts_capacity) { verts_capacity = verts_capacity ? verts_capacity * 2 : 256; verts = realloc(verts, verts_capacity * sizeof(vertex_t)); } const float *mv = stacks[0][tops[0]].m, *p = stacks[1][tops[1]].m, *tm = stacks[2][tops[2]].m; const float e[4] = { mv[0] * x + mv[4] * y + mv[8] * z + mv[12], mv[1] * x + mv[5] * y + mv[9] * z + mv[13], mv[2] * x + mv[6] * y + mv[10] * z + mv[14], mv[3] * x + mv[7] * y + mv[11] * z + mv[15], }; vertex_t *v = &verts[num_verts++]; for (int row = 0; row < 4; ++row) v->pos[row] = p[row] * e[0] + p[4 + row] * e[1] + p[8 + row] * e[2] + p[12 + row] * e[3]; memcpy(v->color, color, sizeof(color)); float s = texcoord[0][0], t = texcoord[0][1]; if (texgen_s || texgen_t) { // GL_SPHERE_MAP: reflect the eye-space view vector about the eye-space normal. float n[3] = { mv[0] * normal[0] + mv[4] * normal[1] + mv[8] * normal[2], mv[1] * normal[0] + mv[5] * normal[1] + mv[9] * normal[2], mv[2] * normal[0] + mv[6] * normal[1] + mv[10] * normal[2], }; float nl = sqrtf(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); float u[3] = {e[0], e[1], e[2]}; float ul = sqrtf(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]); if (nl > 0.0f) for (int i = 0; i < 3; ++i) n[i] /= nl; if (ul > 0.0f) for (int i = 0; i < 3; ++i) u[i] /= ul; const float d = n[0] * u[0] + n[1] * u[1] + n[2] * u[2]; const float r[3] = {u[0] - 2.0f * n[0] * d, u[1] - 2.0f * n[1] * d, u[2] - 2.0f * n[2] * d}; const float m = 2.0f * sqrtf(r[0] * r[0] + r[1] * r[1] + (r[2] + 1.0f) * (r[2] + 1.0f)); if (m > 0.0f) { if (texgen_s) s = r[0] / m + 0.5f; if (texgen_t) t = r[1] / m + 0.5f; } } const float q = tm[3] * s + tm[7] * t + tm[15]; v->uv0[0] = (tm[0] * s + tm[4] * t + tm[12]) / q; v->uv0[1] = (tm[1] * s + tm[5] * t + tm[13]) / q; v->uv1[0] = texcoord[1][0]; v->uv1[1] = texcoord[1][1]; } void cbgl_Vertex3fv(const GLfloat *v) { cbgl_Vertex3f(v[0], v[1], v[2]); } static void emit(const vertex_t *v) { if (num_tris == tris_capacity) { tris_capacity = tris_capacity ? tris_capacity * 2 : 1024; tris = realloc(tris, tris_capacity * sizeof(vertex_t)); } tris[num_tris++] = *v; } static void emit_triangle(const vertex_t *a, const vertex_t *b, const vertex_t *c) { emit(a); emit(b); emit(c); } static GLuint compile(GLenum type, const char *source) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, NULL); glCompileShader(shader); return shader; } static void setup(void) { if (program != 0) return; program = glCreateProgram(); glAttachShader(program, compile(GL_VERTEX_SHADER, "attribute vec4 pos; attribute vec4 color; attribute vec2 uv0; attribute vec2 uv1;\n" "varying vec4 v_color; varying vec2 v_uv0; varying vec2 v_uv1;\n" "void main() { v_color = color; v_uv0 = uv0; v_uv1 = uv1; gl_Position = pos; }\n")); glAttachShader(program, compile(GL_FRAGMENT_SHADER, "precision mediump float;\n" "varying vec4 v_color; varying vec2 v_uv0; varying vec2 v_uv1;\n" "uniform sampler2D s0; uniform sampler2D s1;\n" "uniform int tex0; uniform int tex1; uniform int env0; uniform int env1;\n" "uniform int alpha_func; uniform float alpha_ref;\n" // 0 = GL_MODULATE, 1 = GL_DECAL, 2 = GL_REPLACE "vec4 apply(vec4 c, vec4 t, int env) {\n" " if (env == 1) return vec4(mix(c.rgb, t.rgb, t.a), c.a);\n" " if (env == 2) return t;\n" " return c * t;\n" "}\n" "void main() {\n" " vec4 c = v_color;\n" " if (tex0 == 1) c = apply(c, texture2D(s0, v_uv0), env0);\n" " if (tex1 == 1) c = apply(c, texture2D(s1, v_uv1), env1);\n" // 1 = never, 2 = <, 3 = ==, 4 = <=, 5 = >, 6 = !=, 7 = >=, 0 = always (or no alpha test) " if (alpha_func == 1 || (alpha_func == 2 && !(c.a < alpha_ref)) ||\n" " (alpha_func == 3 && c.a != alpha_ref) || (alpha_func == 4 && !(c.a <= alpha_ref)) ||\n" " (alpha_func == 5 && !(c.a > alpha_ref)) || (alpha_func == 6 && c.a == alpha_ref) ||\n" " (alpha_func == 7 && !(c.a >= alpha_ref))) discard;\n" " gl_FragColor = c;\n" "}\n")); glLinkProgram(program); attr_pos = glGetAttribLocation(program, "pos"); attr_color = glGetAttribLocation(program, "color"); attr_uv0 = glGetAttribLocation(program, "uv0"); attr_uv1 = glGetAttribLocation(program, "uv1"); u_tex0 = glGetUniformLocation(program, "tex0"); u_tex1 = glGetUniformLocation(program, "tex1"); u_env0 = glGetUniformLocation(program, "env0"); u_env1 = glGetUniformLocation(program, "env1"); u_alpha_func = glGetUniformLocation(program, "alpha_func"); u_alpha_ref = glGetUniformLocation(program, "alpha_ref"); glUseProgram(program); glUniform1i(glGetUniformLocation(program, "s0"), 0); glUniform1i(glGetUniformLocation(program, "s1"), 1); glGenBuffers(1, &buffer); } static int env_index(GLenum env) { return env == GL_DECAL ? 1 : env == GL_REPLACE ? 2 : 0; } void cbgl_End(void) { RECORD(OP_END, 0, 0, 0, 0); const int n = num_verts; num_tris = 0; switch (begin_mode) { case GL_TRIANGLES: for (int i = 0; i + 2 < n; i += 3) emit_triangle(&verts[i], &verts[i + 1], &verts[i + 2]); break; case GL_TRIANGLE_STRIP: case GL_QUAD_STRIP: // a quad strip covers the same triangles as a triangle strip for (int i = 0; i + 2 < n; ++i) { if (i % 2 == 0) emit_triangle(&verts[i], &verts[i + 1], &verts[i + 2]); else emit_triangle(&verts[i + 1], &verts[i], &verts[i + 2]); } break; case GL_TRIANGLE_FAN: case GL_POLYGON: for (int i = 1; i + 1 < n; ++i) emit_triangle(&verts[0], &verts[i], &verts[i + 1]); break; case GL_QUADS: for (int i = 0; i + 3 < n; i += 4) { emit_triangle(&verts[i], &verts[i + 1], &verts[i + 2]); emit_triangle(&verts[i], &verts[i + 2], &verts[i + 3]); } break; default: break; // the game draws no points or lines } num_verts = 0; if (num_tris == 0) return; setup(); glUseProgram(program); glUniform1i(u_tex0, texture_enabled[0] && bound[0] != 0); glUniform1i(u_tex1, texture_enabled[1] && bound[1] != 0); glUniform1i(u_env0, env_index(texture_env[0])); glUniform1i(u_env1, env_index(texture_env[1])); int func = 0; if (alpha_test) { switch (alpha_func) { case GL_NEVER: func = 1; break; case GL_LESS: func = 2; break; case GL_EQUAL: func = 3; break; case GL_LEQUAL: func = 4; break; case GL_GREATER: func = 5; break; case GL_NOTEQUAL: func = 6; break; case GL_GEQUAL: func = 7; break; default: func = 0; break; } } glUniform1i(u_alpha_func, func); glUniform1f(u_alpha_ref, alpha_ref); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, num_tris * sizeof(vertex_t), tris, GL_STREAM_DRAW); glEnableVertexAttribArray(attr_pos); glEnableVertexAttribArray(attr_color); glEnableVertexAttribArray(attr_uv0); glEnableVertexAttribArray(attr_uv1); glVertexAttribPointer(attr_pos, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void *)0); glVertexAttribPointer(attr_color, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void *)(4 * sizeof(float))); glVertexAttribPointer(attr_uv0, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void *)(8 * sizeof(float))); glVertexAttribPointer(attr_uv1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void *)(10 * sizeof(float))); glDrawArrays(GL_TRIANGLES, 0, num_tris); } /*===========================================================================*/ void cbgl_CallList(GLuint list) { RECORD(OP_CALL, list, 0, 0, 0); if (list < 1 || list > num_lists || !lists[list].defined) return; const list_t *l = &lists[list]; for (int i = 0; i < l->count; ++i) { const call_t *c = &l->calls[i]; switch (c->op) { case OP_BEGIN: cbgl_Begin((GLenum)c->v[0]); break; case OP_END: cbgl_End(); break; case OP_VERTEX: cbgl_Vertex3f(c->v[0], c->v[1], c->v[2]); break; case OP_TEXCOORD: cbgl_MultiTexCoord2f(c->v[0] == 1 ? GL_TEXTURE1 : GL_TEXTURE0, c->v[1], c->v[2]); break; case OP_COLOR: cbgl_Color4f(c->v[0], c->v[1], c->v[2], c->v[3]); break; case OP_NORMAL: cbgl_Normal3f(c->v[0], c->v[1], c->v[2]); break; case OP_PUSH: cbgl_PushMatrix(); break; case OP_POP: cbgl_PopMatrix(); break; case OP_IDENTITY: cbgl_LoadIdentity(); break; case OP_MODE: cbgl_MatrixMode((GLenum)c->v[0]); break; case OP_TRANSLATE: cbgl_Translatef(c->v[0], c->v[1], c->v[2]); break; case OP_SCALE: cbgl_Scalef(c->v[0], c->v[1], c->v[2]); break; case OP_ROTATE: cbgl_Rotatef(c->v[0], c->v[1], c->v[2], c->v[3]); break; case OP_CALL: cbgl_CallList((GLuint)c->v[0]); break; } } }