aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorRadosław Kujawa <radoslaw.kujawa@c0ff33.net>2026-07-17 14:54:32 +0200
committerRadosław Kujawa <radoslaw.kujawa@c0ff33.net>2026-07-17 14:54:32 +0200
commit2702c90d7a39fca2a3319c5e915fd290cb31fc70 (patch)
tree283c4d851efef7e3a7cd03ba80c87e2bffcdbb2c /examples
parent836167945ec2c7ad5acdf0aa17ce35ec1b9428a6 (diff)
Initial import.
Diffstat (limited to 'examples')
-rw-r--r--examples/Makefile20
-rw-r--r--examples/cube.c112
-rw-r--r--examples/rave.c1153
3 files changed, 1285 insertions, 0 deletions
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000..c2d2176
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,20 @@
+# Build the verite-gl example programs against the in-tree libGL
+
+CC ?= cc
+CFLAGS ?= -O2 -mcpu=603e -fsigned-char -fno-strict-aliasing -ffast-math
+TOP = ..
+GLINC = -I$(TOP)/include
+LIBGL = $(TOP)/lib/libGL.a
+LIBS = -lm
+
+PROGS = cube rave
+
+.PHONY: all clean
+all: $(PROGS)
+
+$(PROGS): %: %.c $(LIBGL)
+ $(CC) $(CFLAGS) $(GLINC) $< $(LIBGL) $(LIBS) -o $@
+
+clean:
+ rm -f $(PROGS)
+
diff --git a/examples/cube.c b/examples/cube.c
new file mode 100644
index 0000000..ec47b13
--- /dev/null
+++ b/examples/cube.c
@@ -0,0 +1,112 @@
+/*
+ * mesacube - THE spinning cube
+ *
+ * Build:
+ * cc -O2 -I$HOME/mesa/include -I$HOME -o cube cube.c \
+ * $HOME/mesa/lib/libGL.a -lm
+ *
+ * Run: VERITE_UCODE=$HOME/v2000gl.uc \
+ * ./cube [seconds] (0 = spin until ^C)
+ */
+#include <sys/time.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <GL/gl.h>
+#include <GL/vrmesa.h>
+
+static volatile sig_atomic_t stop;
+
+static void
+onint(int sig)
+{
+
+ (void)sig;
+ stop = 1;
+}
+
+static const GLfloat faces[6][4][3] = {
+ { {-1,-1,-1},{-1, 1,-1},{ 1, 1,-1},{ 1,-1,-1} }, /* -Z */
+ { {-1,-1, 1},{ 1,-1, 1},{ 1, 1, 1},{-1, 1, 1} }, /* +Z */
+ { {-1,-1,-1},{-1,-1, 1},{-1, 1, 1},{-1, 1,-1} }, /* -X */
+ { { 1,-1,-1},{ 1, 1,-1},{ 1, 1, 1},{ 1,-1, 1} }, /* +X */
+ { {-1,-1,-1},{ 1,-1,-1},{ 1,-1, 1},{-1,-1, 1} }, /* -Y */
+ { {-1, 1,-1},{-1, 1, 1},{ 1, 1, 1},{ 1, 1,-1} }, /* +Y */
+};
+
+int
+main(int argc, char **argv)
+{
+ vrMesaContext ctx;
+ struct timeval t0, t1, tstart;
+ float a = 20.0f, b = 30.0f;
+ double secs = 30.0;
+ int frame = 0, f, i;
+
+ if (argc > 1)
+ secs = atof(argv[1]);
+ signal(SIGINT, onint);
+
+ ctx = vrMesaCreateContext(NULL);
+ if (ctx == NULL)
+ return 1;
+ vrMesaMakeCurrent(ctx);
+ printf("GL_RENDERER: %s (%s)\n", glGetString(GL_RENDERER),
+ glGetString(GL_VERSION));
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+ glClearColor(0.06f, 0.07f, 0.12f, 1);
+ glViewport(0, 0, 640, 480);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glFrustum(-1.0, 1.0, -0.75, 0.75, 1.0, 50.0);
+
+ gettimeofday(&tstart, NULL);
+ t0 = tstart;
+ while (!stop) {
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0, 0, -4.5f);
+ glRotatef(a, 1, 0, 0);
+ glRotatef(b, 0, 1, 0);
+
+ glBegin(GL_QUADS);
+ for (f = 0; f < 6; f++)
+ for (i = 0; i < 4; i++) {
+ const GLfloat *v = faces[f][i];
+
+ /* corner color = position in RGB space */
+ glColor3f((v[0] + 1) * 0.5f,
+ (v[1] + 1) * 0.5f, (v[2] + 1) * 0.5f);
+ glVertex3fv(v);
+ }
+ glEnd();
+
+ vrMesaSwapBuffers();
+ a += 1.1f;
+ b += 1.7f;
+ if (++frame % 100 == 0) {
+ double dt;
+
+ gettimeofday(&t1, NULL);
+ dt = (t1.tv_sec - t0.tv_sec) +
+ (t1.tv_usec - t0.tv_usec) / 1e6;
+ printf("%d frames, %.1f fps\n", frame, 100.0 / dt);
+ t0 = t1;
+ }
+ if (secs > 0) {
+ gettimeofday(&t1, NULL);
+ if ((t1.tv_sec - tstart.tv_sec) +
+ (t1.tv_usec - tstart.tv_usec) / 1e6 >= secs)
+ break;
+ }
+ }
+
+ printf("mesacube done after %d frames\n", frame);
+ vrMesaDestroyContext(ctx);
+ return 0;
+}
diff --git a/examples/rave.c b/examples/rave.c
new file mode 100644
index 0000000..98f30e5
--- /dev/null
+++ b/examples/rave.c
@@ -0,0 +1,1153 @@
+/*
+ * rave - a verite-gl megademo
+ *
+ * Scenes:
+ * 1 starfield + flying cubes
+ * 2 wireframe objects
+ * 3 vectorballs
+ * 4 glenz
+ * 5 rubber twister
+ * 6 plasma
+ * 7 shadebobs
+ *
+ * Build:
+ * cc -O2 -I$HOME/mesa/include -I$HOME -o rave rave.c \
+ * $HOME/mesa/lib/libGL.a -lm
+ * Run: VERITE_UCODE=$HOME/v2000gl.uc \
+ * ./rave [seconds] (0/absent = loop until ^C)
+ */
+#include <sys/time.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <GL/gl.h>
+#include <GL/vrmesa.h>
+
+#define SW 640
+#define SH 480
+
+/* texture object ids */
+#define TEX_CHECK 1 /* orange/white checker (cubes) */
+#define TEX_SPHERE 2 /* radial shaded sphere, binary alpha */
+#define TEX_BLOB 3 /* soft additive blob (shadebobs) */
+#define TEX_ACCUM 5 /* 256^2 shadebobs accumulation buffer */
+
+#define BPM 132.0f
+#define T_FADE 0.7f /* cross-fade ramp length, seconds */
+#define PI 3.14159265358979f
+#define TWO_PI 6.28318530717959f
+
+static volatile sig_atomic_t stop;
+
+static void
+onint(int sig)
+{
+
+ (void)sig;
+ stop = 1;
+}
+
+/* shared helpers */
+
+static struct timeval tstart;
+
+static double
+now(void)
+{
+ struct timeval t;
+
+ gettimeofday(&t, NULL);
+ return (t.tv_sec - tstart.tv_sec) + (t.tv_usec - tstart.tv_usec) / 1e6;
+}
+
+static float
+frand(float a, float b)
+{
+
+ return a + (b - a) * (rand() / (float)RAND_MAX);
+}
+
+/*
+ * Sine lookup table.
+ */
+#define SIN_N 1024
+static float sintab[SIN_N + 1]; /* +1 guard for interp */
+
+static void
+sin_init(void)
+{
+ int i;
+
+ for (i = 0; i <= SIN_N; i++)
+ sintab[i] = sinf(TWO_PI * i / SIN_N);
+}
+
+static float
+fast_sin(float x)
+{
+ float idx = x * (SIN_N / TWO_PI);
+ long i = (long)idx;
+ float frac = idx - (float)i;
+ unsigned k;
+
+ if (frac < 0.0f) { /* floor for negative x */
+ frac += 1.0f;
+ i--;
+ }
+ k = (unsigned)i & (SIN_N - 1);
+ return sintab[k] + (sintab[k + 1] - sintab[k]) * frac;
+}
+
+static float
+fast_cos(float x)
+{
+
+ return fast_sin(x + TWO_PI * 0.25f);
+}
+
+/*
+ * Cheap atan2
+ */
+static float
+fast_atan2(float y, float x)
+{
+ float ax = fabsf(x), ay = fabsf(y);
+ float a, s, r;
+
+ if (ax < 1e-6f && ay < 1e-6f)
+ return 0.0f;
+ a = (ax > ay) ? ay / ax : ax / ay; /* |slope| in [0,1] */
+ s = a * a;
+ r = ((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a;
+ if (ay > ax)
+ r = 1.57079637f - r; /* pi/2 - r */
+ if (x < 0.0f)
+ r = 3.14159274f - r; /* pi - r */
+ if (y < 0.0f)
+ r = -r;
+ return r;
+}
+
+/* rainbow */
+static void
+color_from_angle(float h, GLfloat *rgb)
+{
+
+ rgb[0] = 0.5f + 0.5f * fast_sin(h);
+ rgb[1] = 0.5f + 0.5f * fast_sin(h + 2.0944f); /* +120 deg */
+ rgb[2] = 0.5f + 0.5f * fast_sin(h + 4.1888f); /* +240 deg */
+}
+
+static float
+beat_pulse(float t)
+{
+ float p = fmodf(t * (BPM / 60.0f), 1.0f);
+
+ return 0.5f + 0.5f * fast_cos(TWO_PI * p);
+}
+
+/* procedural textures */
+
+static void
+make_checker_tex(GLuint id, int n)
+{
+ static GLubyte tex[64 * 64 * 4];
+ int x, y;
+
+ for (y = 0; y < n; y++)
+ for (x = 0; x < n; x++) {
+ GLubyte *p = &tex[(y * n + x) * 4];
+ int border = x < 2 || y < 2 || x >= n - 2 || y >= n - 2;
+ int check = ((x / 8 + y / 8) & 1);
+
+ p[0] = border ? 255 : check ? 200 : 255;
+ p[1] = border ? 255 : check ? 60 : 160;
+ p[2] = border ? 255 : check ? 40 : 30;
+ p[3] = 255;
+ }
+ glBindTexture(GL_TEXTURE_2D, id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexImage2D(GL_TEXTURE_2D, 0, 3, n, n, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, tex);
+}
+
+static void
+make_sphere_tex(GLuint id, int n)
+{
+ static GLubyte tex[64 * 64 * 4];
+ const float lx = -0.406f, ly = 0.507f, lz = 0.761f; /* light dir */
+ int x, y;
+
+ for (y = 0; y < n; y++)
+ for (x = 0; x < n; x++) {
+ GLubyte *p = &tex[(y * n + x) * 4];
+ float dx = (x + 0.5f) / n * 2.0f - 1.0f;
+ float dy = (y + 0.5f) / n * 2.0f - 1.0f;
+ float r2 = dx * dx + dy * dy;
+
+ if (r2 <= 1.0f) {
+ float nz = sqrtf(1.0f - r2);
+ float d = dx * lx + dy * ly + nz * lz;
+ float s;
+
+ if (d < 0.0f)
+ d = 0.0f;
+ s = 0.25f + 0.75f * d; /* ambient+diff */
+ p[0] = p[1] = p[2] = (GLubyte)(255.0f * s);
+ p[3] = 255;
+ } else {
+ p[0] = p[1] = p[2] = 0;
+ p[3] = 0;
+ }
+ }
+ glBindTexture(GL_TEXTURE_2D, id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, 4, n, n, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, tex);
+}
+
+/* soft radial blob, intensity in RGB and A, for GL_ONE/GL_ONE additive */
+static void
+make_blob_tex(GLuint id, int n)
+{
+ static GLubyte tex[64 * 64 * 4];
+ int x, y;
+
+ for (y = 0; y < n; y++)
+ for (x = 0; x < n; x++) {
+ GLubyte *p = &tex[(y * n + x) * 4];
+ float dx = (x + 0.5f) / n * 2.0f - 1.0f;
+ float dy = (y + 0.5f) / n * 2.0f - 1.0f;
+ float r2 = dx * dx + dy * dy;
+ float in = expf(-3.2f * r2);
+ GLubyte v = (GLubyte)(255.0f * in);
+
+ p[0] = p[1] = p[2] = v;
+ p[3] = v;
+ }
+ glBindTexture(GL_TEXTURE_2D, id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, 4, n, n, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, tex);
+}
+
+/* shared geometry */
+
+static const GLfloat faces[6][4][3] = {
+ { {-1,-1,-1},{-1, 1,-1},{ 1, 1,-1},{ 1,-1,-1} }, /* -Z */
+ { {-1,-1, 1},{ 1,-1, 1},{ 1, 1, 1},{-1, 1, 1} }, /* +Z */
+ { {-1,-1,-1},{-1,-1, 1},{-1, 1, 1},{-1, 1,-1} }, /* -X */
+ { { 1,-1,-1},{ 1, 1,-1},{ 1, 1, 1},{ 1,-1, 1} }, /* +X */
+ { {-1,-1,-1},{ 1,-1,-1},{ 1,-1, 1},{-1,-1, 1} }, /* -Y */
+ { {-1, 1,-1},{-1, 1, 1},{ 1, 1, 1},{ 1, 1,-1} }, /* +Y */
+};
+static const GLfloat uv[4][2] = { {0,0},{1,0},{1,1},{0,1} };
+
+static const GLfloat octa_v[6][3] = {
+ { 1, 0, 0 }, {-1, 0, 0 }, { 0, 1, 0 },
+ { 0,-1, 0 }, { 0, 0, 1 }, { 0, 0,-1 },
+};
+static const int octa_f[8][3] = {
+ { 0, 2, 4 }, { 2, 1, 4 }, { 1, 3, 4 }, { 3, 0, 4 },
+ { 2, 0, 5 }, { 1, 2, 5 }, { 3, 1, 5 }, { 0, 3, 5 },
+};
+
+/* torus (scene 2): verts + an edge (index-pair) list for AA GL_LINES */
+#define TOR_RINGS 12
+#define TOR_SIDES 8
+#define TOR_NV (TOR_RINGS * TOR_SIDES)
+#define TOR_NE (TOR_NV * 2)
+static GLfloat tor_v[TOR_NV][3];
+static int tor_e[TOR_NE][2];
+
+static void
+gen_torus(float R, float r)
+{
+ int i, j, e = 0;
+
+ for (i = 0; i < TOR_RINGS; i++) {
+ float phi = TWO_PI * i / TOR_RINGS;
+
+ for (j = 0; j < TOR_SIDES; j++) {
+ float th = TWO_PI * j / TOR_SIDES;
+ float rr = R + r * cosf(th);
+ int idx = i * TOR_SIDES + j;
+ int jn = (j + 1) % TOR_SIDES;
+ int in = (i + 1) % TOR_RINGS;
+
+ tor_v[idx][0] = rr * cosf(phi);
+ tor_v[idx][1] = r * sinf(th);
+ tor_v[idx][2] = rr * sinf(phi);
+
+ tor_e[e][0] = idx; /* around tube */
+ tor_e[e][1] = i * TOR_SIDES + jn;
+ e++;
+ tor_e[e][0] = idx; /* along tube */
+ tor_e[e][1] = in * TOR_SIDES + j;
+ e++;
+ }
+ }
+}
+
+/* baseline GL state + fullscreen fade overlay */
+
+static void
+scene_reset_gl(void)
+{
+
+ glDisable(GL_BLEND);
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_ALPHA_TEST);
+ glDisable(GL_FOG);
+ glDisable(GL_POINT_SMOOTH);
+ glDisable(GL_LINE_SMOOTH);
+ glDisable(GL_SCISSOR_TEST);
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ glDepthMask(GL_TRUE);
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ glColorMask(1, 1, 1, 1);
+ glShadeModel(GL_SMOOTH);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(1, 1, 1, 1);
+ glLineWidth(1);
+ glPointSize(1);
+ glViewport(0, 0, SW, SH);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glFrustum(-1.0, 1.0, -0.75, 0.75, 1.0, 50.0);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+}
+
+static void
+overlay_black(float a)
+{
+
+ if (a <= 0.004f)
+ return;
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0, 1, 0, 1, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_FOG);
+ glDisable(GL_CULL_FACE);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0, 0, 0, a);
+ glBegin(GL_QUADS);
+ glVertex2f(0, 0);
+ glVertex2f(1, 0);
+ glVertex2f(1, 1);
+ glVertex2f(0, 1);
+ glEnd();
+
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+}
+
+/* scene 1: starfield + flying spinning cubes */
+
+#define NSTAR 220
+#define NCUBE 6
+
+static struct { float x, y, z; } star[NSTAR];
+static struct { float dir, z, a, b, av, bv, zv; } cube[NCUBE];
+
+static void
+init_stars(void)
+{
+ int i;
+
+ make_checker_tex(TEX_CHECK, 64);
+ for (i = 0; i < NSTAR; i++) {
+ star[i].x = frand(-6, 6);
+ star[i].y = frand(-4.5f, 4.5f);
+ star[i].z = frand(-40, -1);
+ }
+ for (i = 0; i < NCUBE; i++) {
+ cube[i].dir = frand(0, TWO_PI);
+ cube[i].z = frand(-42, -6);
+ cube[i].a = frand(0, 360);
+ cube[i].b = frand(0, 360);
+ cube[i].av = frand(30, 70);
+ cube[i].bv = frand(30, 70);
+ cube[i].zv = frand(5, 8);
+ }
+}
+
+static void
+draw_stars(float t, float dt)
+{
+ int i, f, k;
+
+ (void)t;
+ glClearColor(0.02f, 0.02f, 0.06f, 1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ /* stars: native AA points, additive-free coverage blend, no depth */
+ glDepthMask(GL_FALSE);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_FOG);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_POINT_SMOOTH);
+ glPointSize(2);
+ glBegin(GL_POINTS);
+ for (i = 0; i < NSTAR; i++) {
+ float b = 1.0f - (-star[i].z) / 44.0f;
+
+ if (b < 0.05f)
+ b = 0.05f;
+ glColor4f(b, b, b, b);
+ glVertex3f(star[i].x, star[i].y, star[i].z);
+ }
+ glEnd();
+ for (i = 0; i < NSTAR; i++) {
+ star[i].z += 13.0f * dt;
+ if (star[i].z > -0.8f) {
+ star[i].z = -40;
+ star[i].x = frand(-6, 6);
+ star[i].y = frand(-4.5f, 4.5f);
+ }
+ }
+
+ /* cubes: textured Gouraud, depth + cull, linear fog for depth cue */
+ glDepthMask(GL_TRUE);
+ glEnable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+ glDisable(GL_POINT_SMOOTH);
+ glEnable(GL_CULL_FACE);
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, TEX_CHECK);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+ glEnable(GL_FOG);
+ glFogi(GL_FOG_MODE, GL_LINEAR);
+ glFogf(GL_FOG_START, 10);
+ glFogf(GL_FOG_END, 42);
+ {
+ GLfloat fc[4] = { 0.02f, 0.02f, 0.06f, 1 };
+
+ glFogfv(GL_FOG_COLOR, fc);
+ }
+ for (k = 0; k < NCUBE; k++) {
+ float approach = (cube[k].z + 42.0f) / 41.0f; /* 0 far .. 1 near */
+ float rad, ox, oy;
+
+ if (approach < 0.0f)
+ approach = 0.0f;
+ /* offset grows ~quadratically as the cube nears, so perspective
+ * sweeps it out to a screen edge well before it reaches the eye */
+ rad = 0.35f + approach * approach * 7.0f;
+ ox = cosf(cube[k].dir) * rad;
+ oy = sinf(cube[k].dir) * rad * 0.72f; /* 4:3 vertical squash */
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(ox, oy, cube[k].z);
+ glRotatef(cube[k].a, 1, 0, 0);
+ glRotatef(cube[k].b, 0, 1, 0);
+ glScalef(0.7f, 0.7f, 0.7f);
+ glBegin(GL_QUADS);
+ for (f = 0; f < 6; f++) {
+ GLfloat rgb[3];
+ int j;
+
+ color_from_angle(f * 1.0f + k * 1.3f, rgb);
+ glColor3f(0.5f + 0.5f * rgb[0], 0.5f + 0.5f * rgb[1],
+ 0.5f + 0.5f * rgb[2]);
+ for (j = 0; j < 4; j++) {
+ glTexCoord2f(uv[j][0], uv[j][1]);
+ glVertex3fv(faces[f][j]);
+ }
+ }
+ glEnd();
+
+ cube[k].z += cube[k].zv * dt;
+ cube[k].a += cube[k].av * dt;
+ cube[k].b += cube[k].bv * dt;
+ if (cube[k].z > -3.0f) { /* recycle before it looms */
+ cube[k].z = -42;
+ cube[k].dir = frand(0, TWO_PI);
+ cube[k].a = frand(0, 360);
+ cube[k].b = frand(0, 360);
+ }
+ }
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_FOG);
+}
+
+/* scene 2: wireframe objects */
+
+static void
+draw_wire(float t, float dt)
+{
+ int i;
+
+ (void)dt;
+ glClearColor(0.03f, 0.02f, 0.05f, 1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0, 0, -5.0f);
+ glRotatef(t * 42.0f, 1, 0.35f, 0);
+ glRotatef(t * 27.0f, 0, 1, 0.2f);
+
+ if (t < 10.0f) {
+ /*
+ * native hardware AA lines.
+ */
+ gen_torus(1.1f, 0.42f + 0.12f * sinf(t * 1.6f));
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_LINE_SMOOTH);
+ glLineWidth(1);
+ glBegin(GL_LINES);
+ for (i = 0; i < TOR_NE; i++) {
+ int a = tor_e[i][0], b = tor_e[i][1];
+ GLfloat rgb[3];
+
+ color_from_angle(tor_v[a][1] * 3.0f + t, rgb);
+ glColor3fv(rgb);
+ glVertex3fv(tor_v[a]);
+ color_from_angle(tor_v[b][1] * 3.0f + t, rgb);
+ glColor3fv(rgb);
+ glVertex3fv(tor_v[b]);
+ }
+ glEnd();
+ glDisable(GL_LINE_SMOOTH);
+ glDisable(GL_BLEND);
+ } else {
+ /*
+ * the same idea the OTHER hardware way
+ */
+ glDisable(GL_BLEND);
+ glDisable(GL_LINE_SMOOTH);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ glScalef(1.6f, 1.6f, 1.6f);
+ glBegin(GL_TRIANGLES);
+ for (i = 0; i < 8; i++) {
+ GLfloat rgb[3];
+ int j;
+
+ color_from_angle(i * 0.8f + t, rgb);
+ glColor3fv(rgb);
+ for (j = 0; j < 3; j++)
+ glVertex3fv(octa_v[octa_f[i][j]]);
+ }
+ glEnd();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+}
+
+/* scene 3: vectorballs */
+
+#define VB_X 16
+#define VB_Y 8
+#define VB_CAMZ 15.0f
+
+static void
+init_vballs(void)
+{
+
+ make_sphere_tex(TEX_SPHERE, 64);
+}
+
+static void
+draw_vballs(float t, float dt)
+{
+ float ry = t * 0.4f, rx = 0.3f * sinf(t * 0.5f);
+ float cy = cosf(ry), sy = sinf(ry);
+ float cx = cosf(rx), sx = sinf(rx);
+ float s = 0.42f * (0.85f + 0.3f * beat_pulse(t));
+ int gx, gz;
+
+ (void)dt;
+ glClearColor(0.03f, 0.04f, 0.08f, 1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ /* crisp round balls: alpha-test GREATER + depth, NO blend, NO sort */
+ glEnable(GL_DEPTH_TEST);
+ glDepthMask(GL_TRUE);
+ glDisable(GL_BLEND);
+ glDisable(GL_CULL_FACE);
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, TEX_SPHERE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+ glEnable(GL_ALPHA_TEST);
+ glAlphaFunc(GL_GREATER, 0.5f);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity(); /* balls are pre-transformed in eye space */
+ glBegin(GL_QUADS);
+ for (gz = 0; gz < VB_Y; gz++)
+ for (gx = 0; gx < VB_X; gx++) {
+ float px = (gx - (VB_X - 1) * 0.5f) * 0.85f;
+ float pz = (gz - (VB_Y - 1) * 0.5f) * 0.85f;
+ float py = 1.8f * sinf(px * 0.5f + pz * 0.6f + t * 1.5f);
+ float x1, y1, z1, x2, y2, z2, cxx, cyy, czz;
+ GLfloat rgb[3];
+
+ /* rotate lattice about Y then X on the CPU */
+ x1 = px * cy + pz * sy;
+ z1 = -px * sy + pz * cy;
+ y1 = py;
+ x2 = x1;
+ y2 = y1 * cx - z1 * sx;
+ z2 = y1 * sx + z1 * cx;
+
+ cxx = x2;
+ cyy = y2;
+ czz = z2 - VB_CAMZ;
+
+ color_from_angle(gx * 0.3f + gz * 0.25f, rgb);
+ glColor3f(0.4f + 0.6f * rgb[0], 0.4f + 0.6f * rgb[1],
+ 0.4f + 0.6f * rgb[2]);
+ glTexCoord2f(0, 0); glVertex3f(cxx - s, cyy - s, czz);
+ glTexCoord2f(1, 0); glVertex3f(cxx + s, cyy - s, czz);
+ glTexCoord2f(1, 1); glVertex3f(cxx + s, cyy + s, czz);
+ glTexCoord2f(0, 1); glVertex3f(cxx - s, cyy + s, czz);
+ }
+ glEnd();
+ glDisable(GL_ALPHA_TEST);
+ glDisable(GL_TEXTURE_2D);
+}
+
+/* scene 4: glenz */
+
+static void
+draw_glenz(float t, float dt)
+{
+ float A = 0.42f + 0.14f * beat_pulse(t);
+ int f, j;
+
+ (void)dt;
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_TEXTURE_2D);
+ glShadeModel(GL_FLAT);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+
+ /* glenz cube */
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0, 0, -4.5f);
+ glRotatef(t * 33.0f, 1, 0, 0);
+ glRotatef(t * 44.0f, 0, 1, 0);
+ glBegin(GL_QUADS);
+ for (f = 0; f < 6; f++) {
+ GLfloat rgb[3];
+
+ color_from_angle(f * 1.05f + t * 0.7f, rgb);
+ glColor4f(rgb[0], rgb[1], rgb[2], A);
+ for (j = 0; j < 4; j++)
+ glVertex3fv(faces[f][j]);
+ }
+ glEnd();
+
+ /* counter-rotating glenz octahedron */
+ glLoadIdentity();
+ glTranslatef(0, 0, -4.5f);
+ glRotatef(-t * 38.0f, 0, 1, 0);
+ glRotatef(t * 26.0f, 1, 0, 0);
+ glScalef(1.35f, 1.35f, 1.35f);
+ glBegin(GL_TRIANGLES);
+ for (f = 0; f < 8; f++) {
+ GLfloat rgb[3];
+
+ color_from_angle(f * 0.8f + 3.0f + t * 0.9f, rgb);
+ glColor4f(rgb[0], rgb[1], rgb[2], A);
+ for (j = 0; j < 3; j++)
+ glVertex3fv(octa_v[octa_f[f][j]]);
+ }
+ glEnd();
+
+ glDisable(GL_BLEND);
+ glShadeModel(GL_SMOOTH);
+}
+
+/* scene 5: rubber twister */
+
+#define TW_BARS 32
+#define TUN_GX 24 /* tunnel grid columns */
+#define TUN_GY 18 /* tunnel grid rows */
+#define TUN_RMAX 400.0f /* screen half-diagonal (radius norm) */
+
+static void
+tunnel_color(float rn, float ang, float t, GLfloat *rgb)
+{
+ float dist = 1.0f / (rn + 0.13f); /* centre reads as far */
+ float band = 0.5f + 0.5f * fast_sin(dist * 2.5f - t * 2.5f);
+ float rip = 0.6f + 0.4f * fast_sin(ang * 6.0f + t * 1.5f);
+ float v = (0.35f + 0.65f * band) * rip;
+
+ color_from_angle(dist * 0.8f + ang + t * 0.3f, rgb);
+ rgb[0] *= v;
+ rgb[1] *= v;
+ rgb[2] *= v;
+}
+
+static float tun_x[TUN_GX + 1];
+static float tun_y[TUN_GY + 1];
+static float tun_rn[TUN_GY + 1][TUN_GX + 1];
+static float tun_ang[TUN_GY + 1][TUN_GX + 1];
+
+static void
+tunnel_init(void)
+{
+ int ix, iy;
+
+ for (ix = 0; ix <= TUN_GX; ix++)
+ tun_x[ix] = -320.0f + 640.0f * ix / TUN_GX;
+ for (iy = 0; iy <= TUN_GY; iy++)
+ tun_y[iy] = -240.0f + 480.0f * iy / TUN_GY;
+ for (iy = 0; iy <= TUN_GY; iy++)
+ for (ix = 0; ix <= TUN_GX; ix++) {
+ float x = tun_x[ix], y = tun_y[iy];
+
+ tun_rn[iy][ix] = sqrtf(x * x + y * y) / TUN_RMAX;
+ tun_ang[iy][ix] = fast_atan2(y, x);
+ }
+}
+
+static void
+draw_tunnel(float t)
+{
+ float rot = t * 0.25f;
+ int ix, iy;
+
+ for (iy = 0; iy < TUN_GY; iy++) {
+ glBegin(GL_QUAD_STRIP);
+ for (ix = 0; ix <= TUN_GX; ix++) {
+ GLfloat rgb[3];
+
+ tunnel_color(tun_rn[iy][ix], tun_ang[iy][ix] + rot,
+ t, rgb);
+ glColor3fv(rgb);
+ glVertex2f(tun_x[ix], tun_y[iy]);
+ tunnel_color(tun_rn[iy + 1][ix],
+ tun_ang[iy + 1][ix] + rot, t, rgb);
+ glColor3fv(rgb);
+ glVertex2f(tun_x[ix], tun_y[iy + 1]);
+ }
+ glEnd();
+ }
+}
+
+static void
+draw_twist(float t, float dt)
+{
+ int i;
+
+ (void)dt;
+ glClearColor(0, 0, 0, 1);
+ glDepthMask(GL_TRUE); /* so the depth buffer clears */
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ /* --- background: whole-screen Gouraud tunnel (ortho) --- */
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE); /* tunnel must not write depth */
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_BLEND);
+ glDisable(GL_TEXTURE_2D);
+ glShadeModel(GL_SMOOTH);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(-320, 320, -240, 240, -1, 1); /* screen-centred pixels */
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ draw_tunnel(t);
+
+ glDepthMask(GL_TRUE);
+ glEnable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE); /* facing flips as it twists */
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glFrustum(-1.0, 1.0, -0.75, 0.75, 1.0, 50.0);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0, 0, -4.2f);
+ glRotatef(12.0f * fast_sin(t * 0.3f), 1, 0, 0);
+
+ glBegin(GL_QUAD_STRIP);
+ for (i = 0; i <= TW_BARS; i++) {
+ int d = TW_BARS - i; /* 0 at top, grows downward */
+ float th = t * 1.4f + d * 0.30f;
+ float R = 0.5f + 0.25f * fast_sin(t * 1.7f + d * 0.4f);
+ float y = 1.4f - 2.8f * i / TW_BARS; /* top -> bottom */
+ float c = fast_cos(th), s = fast_sin(th);
+ GLfloat rgb[3];
+
+ color_from_angle(th, rgb);
+ glColor3fv(rgb);
+ glVertex3f(-R * c, y, -R * s);
+ glVertex3f(R * c, y, R * s);
+ }
+ glEnd();
+}
+
+/* scene 6: plasma (vertex-colour Gouraud mesh) */
+
+#define PL_GX 24
+#define PL_GY 18
+
+static void
+plasma_color(float u, float v, float t, GLfloat *rgb)
+{
+ float s;
+
+ float dx = u - 0.5f, dy = v - 0.5f;
+
+ s = fast_sin(u * 6.0f + t * 1.3f)
+ + fast_sin(v * 5.0f - t * 1.1f)
+ + fast_sin((u + v) * 4.0f + t * 0.7f)
+ + fast_sin(sqrtf(dx * dx + dy * dy) * 9.0f - t * 1.7f);
+ rgb[0] = 0.5f + 0.5f * fast_sin(s * 1.05f);
+ rgb[1] = 0.5f + 0.5f * fast_sin(s * 1.05f + 2.0944f);
+ rgb[2] = 0.5f + 0.5f * fast_sin(s * 1.05f + 4.1888f);
+}
+
+static void
+draw_plasma(float t, float dt)
+{
+ int x, y;
+
+ (void)dt;
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_BLEND);
+ glDisable(GL_TEXTURE_2D);
+ glShadeModel(GL_SMOOTH);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, 1, 0, 1, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ /* one GL_QUAD_STRIP per row halves the glVertex count (900 verts) */
+ for (y = 0; y < PL_GY; y++) {
+ glBegin(GL_QUAD_STRIP);
+ for (x = 0; x <= PL_GX; x++) {
+ float u = (float)x / PL_GX;
+ float v0 = (float)y / PL_GY;
+ float v1 = (float)(y + 1) / PL_GY;
+ GLfloat rgb[3];
+
+ plasma_color(u, v0, t, rgb);
+ glColor3fv(rgb);
+ glVertex2f(u, v0);
+ plasma_color(u, v1, t, rgb);
+ glColor3fv(rgb);
+ glVertex2f(u, v1);
+ }
+ glEnd();
+ }
+}
+
+/* scene 7: shadebobs (persistent accum texture via bitblt) */
+
+#define NBOB 6
+#define ACC_N 128
+
+static struct { float fx, fy, phx, phy, r, g, b; } bob[NBOB];
+
+static void
+init_bobs(void)
+{
+ static GLubyte zero[ACC_N * ACC_N * 3]; /* BSS: already zero */
+ int i;
+
+ make_blob_tex(TEX_BLOB, 64);
+
+ /* accum texture: specify + draw once so the copy hits the HW bitblt
+ * path, not the re-upload fallback (mesaconf.c residency idiom) */
+ glBindTexture(GL_TEXTURE_2D, TEX_ACCUM);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, 3, ACC_N, ACC_N, 0, GL_RGB,
+ GL_UNSIGNED_BYTE, zero);
+
+ glViewport(0, 0, ACC_N, ACC_N);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, 1, 0, 1, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glColor3f(1, 1, 1);
+ glEnable(GL_TEXTURE_2D);
+ glBegin(GL_QUADS); /* prime residency */
+ glTexCoord2f(0, 0); glVertex2f(0, 0);
+ glTexCoord2f(1, 0); glVertex2f(1, 0);
+ glTexCoord2f(1, 1); glVertex2f(1, 1);
+ glTexCoord2f(0, 1); glVertex2f(0, 1);
+ glEnd();
+ glDisable(GL_TEXTURE_2D);
+
+ for (i = 0; i < NBOB; i++) {
+ GLfloat rgb[3];
+
+ bob[i].fx = frand(0.5f, 1.7f);
+ bob[i].fy = frand(0.5f, 1.7f);
+ bob[i].phx = frand(0, TWO_PI);
+ bob[i].phy = frand(0, TWO_PI);
+ color_from_angle(i * 1.05f, rgb);
+ bob[i].r = rgb[0];
+ bob[i].g = rgb[1];
+ bob[i].b = rgb[2];
+ }
+}
+
+static void
+bob_quad(float cx, float cy, float s)
+{
+
+ glTexCoord2f(0, 0); glVertex2f(cx - s, cy - s);
+ glTexCoord2f(1, 0); glVertex2f(cx + s, cy - s);
+ glTexCoord2f(1, 1); glVertex2f(cx + s, cy + s);
+ glTexCoord2f(0, 1); glVertex2f(cx - s, cy + s);
+}
+
+static void
+draw_bobs(float t, float dt)
+{
+ float bp = 0.6f + 0.4f * beat_pulse(t);
+ int i;
+
+ (void)dt;
+
+ /* ---- pass A: 256^2 scratch -- fade accum, add bobs, copy back ---- */
+ glViewport(0, 0, ACC_N, ACC_N);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, ACC_N, 0, ACC_N, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+
+ /* decay: draw accum * 0.92 (no blend) */
+ glBindTexture(GL_TEXTURE_2D, TEX_ACCUM);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+ glDisable(GL_BLEND);
+ glEnable(GL_TEXTURE_2D);
+ glColor3f(0.92f, 0.92f, 0.92f);
+ glBegin(GL_QUADS);
+ glTexCoord2f(0, 0); glVertex2f(0, 0);
+ glTexCoord2f(1, 0); glVertex2f(ACC_N, 0);
+ glTexCoord2f(1, 1); glVertex2f(ACC_N, ACC_N);
+ glTexCoord2f(0, 1); glVertex2f(0, ACC_N);
+ glEnd();
+
+ /* new bobs: additive FUNC_ADD ONE/ONE (no alpha-uniformity concern) */
+ glBindTexture(GL_TEXTURE_2D, TEX_BLOB);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE);
+ glBegin(GL_QUADS);
+ for (i = 0; i < NBOB; i++) {
+ float cx = ACC_N * (0.5f + 0.36f * sinf(t * bob[i].fx + bob[i].phx));
+ float cy = ACC_N * (0.5f + 0.36f * sinf(t * bob[i].fy + bob[i].phy));
+
+ glColor3f(bob[i].r * bp * 0.55f, bob[i].g * bp * 0.55f,
+ bob[i].b * bp * 0.55f);
+ bob_quad(cx, cy, ACC_N * 0.16f);
+ }
+ glEnd();
+ glDisable(GL_BLEND);
+ glDisable(GL_TEXTURE_2D);
+
+ /* feed the freshly-drawn 256^2 region back into the accum texture */
+ glBindTexture(GL_TEXTURE_2D, TEX_ACCUM);
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, ACC_N, ACC_N);
+
+ /* ---- pass B: present the accumulation scaled to fullscreen ---- */
+ glViewport(0, 0, SW, SH);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, 1, 0, 1, -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glBindTexture(GL_TEXTURE_2D, TEX_ACCUM);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glDisable(GL_BLEND);
+ glEnable(GL_TEXTURE_2D);
+ glColor3f(1, 1, 1);
+ glBegin(GL_QUADS);
+ glTexCoord2f(0, 0); glVertex2f(0, 0);
+ glTexCoord2f(1, 0); glVertex2f(1, 0);
+ glTexCoord2f(1, 1); glVertex2f(1, 1);
+ glTexCoord2f(0, 1); glVertex2f(0, 1);
+ glEnd();
+ glDisable(GL_TEXTURE_2D);
+}
+
+/* scene table + main */
+
+struct scene {
+ const char *name;
+ void (*init)(void);
+ void (*render)(float t, float dt);
+ float dur;
+};
+
+static const struct scene scenes[] = {
+ { "starfield", init_stars, draw_stars, 18.0f },
+ { "wire", NULL, draw_wire, 16.0f },
+ { "vballs", init_vballs, draw_vballs, 18.0f },
+ { "glenz", NULL, draw_glenz, 16.0f },
+ { "twister", NULL, draw_twist, 16.0f },
+ { "plasma", NULL, draw_plasma, 18.0f },
+ { "shadebobs", init_bobs, draw_bobs, 20.0f },
+};
+#define NSCENE (int)(sizeof scenes / sizeof scenes[0])
+
+static int inited[NSCENE];
+
+static void
+write_shot(const char *name)
+{
+ static GLubyte buf[SW * SH * 3];
+ char path[128];
+ FILE *f;
+ int y;
+
+ glReadPixels(0, 0, SW, SH, GL_RGB, GL_UNSIGNED_BYTE, buf);
+ snprintf(path, sizeof path, "/tmp/mesarave_%s.ppm", name);
+ if ((f = fopen(path, "wb")) == NULL)
+ return;
+ fprintf(f, "P6\n%d %d\n255\n", SW, SH);
+ for (y = SH - 1; y >= 0; y--) /* GL bottom-up -> PPM top-down */
+ fwrite(buf + (size_t)y * SW * 3, 1, SW * 3, f);
+ fclose(f);
+ printf("shot: %s\n", path);
+}
+
+int
+main(int argc, char **argv)
+{
+ vrMesaContext ctx;
+ const char *lockenv = getenv("MESARAVE_SCENE"); /* lock 1 scene */
+ const char *shotenv = getenv("MESARAVE_SHOT"); /* dump PPM at frame N */
+ double cap = (argc > 1) ? atof(argv[1]) : 0.0;
+ double cycle = 0.0, tlast = 0.0;
+ int cur = -1, frame = 0, i, lock = -1;
+ int shot_at = shotenv ? atoi(shotenv) : 0, shotcnt = 0;
+
+ for (i = 0; i < NSCENE; i++)
+ cycle += scenes[i].dur;
+ if (lockenv != NULL) {
+ lock = atoi(lockenv);
+ if (lock < 0 || lock >= NSCENE)
+ lock = -1;
+ }
+ srand(20260715);
+ sin_init();
+ tunnel_init();
+ signal(SIGINT, onint);
+
+ ctx = vrMesaCreateContext(NULL); /* NULL => $VERITE_UCODE */
+ if (ctx == NULL)
+ return 1;
+ vrMesaMakeCurrent(ctx);
+ printf("GL_RENDERER: %s (%s)\n", glGetString(GL_RENDERER),
+ glGetString(GL_VERSION));
+
+ gettimeofday(&tstart, NULL);
+ while (!stop) {
+ double T = now();
+ double dt = T - tlast;
+ int idx;
+ float st, a = 0.0f, d;
+
+ tlast = T;
+ if (lock >= 0) {
+ idx = lock; /* single scene, no cycle/fade */
+ st = (float)T;
+ } else {
+ double c = fmod(T, cycle), acc = 0.0;
+
+ for (idx = 0; idx < NSCENE; idx++) {
+ if (c < acc + scenes[idx].dur)
+ break;
+ acc += scenes[idx].dur;
+ }
+ if (idx >= NSCENE)
+ idx = NSCENE - 1;
+ st = (float)(c - acc);
+ }
+
+ if (idx != cur) { /* scene boundary */
+ scene_reset_gl();
+ if (!inited[idx] && scenes[idx].init != NULL) {
+ scenes[idx].init();
+ inited[idx] = 1;
+ }
+ cur = idx;
+ }
+
+ if (dt < 0.0 || dt > 0.25)
+ dt = 1.0 / 60.0; /* clamp wrap/first frame */
+ scenes[idx].render(st, (float)dt);
+
+ if (shot_at > 0 && ++shotcnt >= shot_at) {
+ write_shot(scenes[idx].name); /* grab back buffer, exit */
+ break;
+ }
+
+ /* cross-fade black at each scene's head and tail */
+ if (lock < 0) {
+ d = scenes[idx].dur;
+ if (st < T_FADE)
+ a = 1.0f - st / T_FADE;
+ else if (st > d - T_FADE)
+ a = (st - (d - T_FADE)) / T_FADE;
+ }
+ overlay_black(a);
+
+ vrMesaSwapBuffers();
+ if (++frame % 100 == 0)
+ printf("%s %d frames\n", scenes[cur].name, frame);
+ if (cap > 0.0 && T >= cap)
+ break;
+ }
+
+ printf("mesarave done after %d frames\n", frame);
+ vrMesaDestroyContext(ctx);
+ return 0;
+}