Start the API.
[movit] / test.cpp
1 #define GL_GLEXT_PROTOTYPES 1
2 #define NO_SDL_GLEXT 1
3
4 #define WIDTH 1280
5 #define HEIGHT 720
6 #define BUFFER_OFFSET(i) ((char *)NULL + (i))
7
8 #define HSV_WHEEL_SIZE 128
9
10 #include <math.h>
11 #include <time.h>
12
13 #include <string>
14 #include <vector>
15
16 #include <SDL/SDL.h>
17 #include <SDL/SDL_opengl.h>
18 #include <SDL/SDL_image.h>
19
20 #include <GL/gl.h>
21 #include <GL/glext.h>
22
23 #ifdef NDEBUG
24 #define check_error()
25 #else
26 #define check_error() { int err = glGetError(); if (err != GL_NO_ERROR) { printf("GL error 0x%x at line %d\n", err, __LINE__); exit(1); } }
27 #endif
28
29 unsigned char result[WIDTH * HEIGHT * 4];
30
31 float lift_theta = 0.0f, lift_rad = 0.0f, lift_v = 0.0f;
32 float gamma_theta = 0.0f, gamma_rad = 0.0f, gamma_v = 0.5f;
33 float gain_theta = 0.0f, gain_rad = 0.0f, gain_v = 0.25f;
34 float saturation = 1.0f;
35
36 float lift_r = 0.0f, lift_g = 0.0f, lift_b = 0.0f;
37 float gamma_r = 1.0f, gamma_g = 1.0f, gamma_b = 1.0f;
38 float gain_r = 1.0f, gain_g = 1.0f, gain_b = 1.0f;
39
40 struct ImageFormat {
41         enum { FORMAT_RGB, FORMAT_RGBA } format;
42
43         // Note: sRGB and 709 use the same colorspace primaries.
44         enum { COLORSPACE_sRGB, COLORSPACE_REC_601_525, COLORSPACE_REC_601_625, COLORSPACE_REC_709 } color_space;
45
46         // Note: Rec. 601 and 709 use the same gamma curve.
47         enum { LINEAR_LIGHT, GAMMA_sRGB, GAMMA_REC_601, GAMMA_REC_709 } gamma_curve;
48 };
49
50 enum EffectId {
51         // Mostly for internal use.
52         GAMMA_CONVERSION = 0,
53         RGB_PRIMARIES_CONVERSION,
54
55         // Color.
56         LIFT_GAMMA_GAIN,
57 };
58
59 class Effect {
60 public: 
61         virtual bool needs_linear_light() { return true; }
62         virtual bool needs_srgb_primaries() { return true; }
63         virtual bool needs_many_samples() { return false; }
64         virtual bool needs_mipmaps() { return false; }
65         bool set_float(const std::string& key, float value);
66         bool set_float_array(const std::string&, const float *values, size_t num_values);
67
68 private:
69         bool register_float(const std::string& key, float value);
70         bool register_float_array(const std::string& key, float *values, size_t num_values);
71 };      
72
73 class EffectChain {
74 public:
75         void set_size(unsigned width, unsigned height);
76         void add_input(const ImageFormat &format);
77         Effect *add_effect(EffectId effect);
78         void add_output(const ImageFormat &format);
79
80         void render(unsigned char *src, unsigned char *dst);
81
82 private:
83         unsigned width, height;
84         ImageFormat input_format, output_format;
85         std::vector<Effect *> effects;
86 };
87
88 enum textures {
89         SOURCE_IMAGE = 1,
90         SRGB_LUT = 2,
91         SRGB_REVERSE_LUT = 3,
92         HSV_WHEEL = 4,
93 };
94
95 // assumes h in [0, 2pi> or [-pi, pi>
96 void hsv2rgb(float h, float s, float v, float *r, float *g, float *b)
97 {
98         if (h < 0.0f) {
99                 h += 2.0f * M_PI;
100         }
101         float c = v * s;
102         float hp = (h * 180.0 / M_PI) / 60.0;
103         float x = c * (1 - fabs(fmod(hp, 2.0f) - 1.0f));
104
105         if (hp >= 0 && hp < 1) {
106                 *r = c;
107                 *g = x;
108                 *b = 0.0f;
109         } else if (hp >= 1 && hp < 2) {
110                 *r = x;
111                 *g = c;
112                 *b = 0.0f;
113         } else if (hp >= 2 && hp < 3) {
114                 *r = 0.0f;
115                 *g = c;
116                 *b = x;
117         } else if (hp >= 3 && hp < 4) {
118                 *r = 0.0f;
119                 *g = x;
120                 *b = c;
121         } else if (hp >= 4 && hp < 5) {
122                 *r = x;
123                 *g = 0.0f;
124                 *b = c;
125         } else {
126                 *r = c;
127                 *g = 0.0f;
128                 *b = x;
129         }
130
131         float m = v - c;
132         *r += m;
133         *g += m;
134         *b += m;
135 }
136
137 GLhandleARB read_shader(const char* filename, GLenum type)
138 {
139         static char buf[131072];
140         FILE *fp = fopen(filename, "r");
141         if (fp == NULL) {
142                 perror(filename);
143                 exit(1);
144         }
145
146         int len = fread(buf, 1, sizeof(buf), fp);
147         fclose(fp);
148
149         GLhandleARB obj = glCreateShaderObjectARB(type);
150         const GLchar* source[] = { buf };
151         const GLint length[] = { len };
152         glShaderSource(obj, 1, source, length);
153         glCompileShader(obj);
154
155         GLchar info_log[4096];
156         GLsizei log_length = sizeof(info_log) - 1;
157         glGetShaderInfoLog(obj, log_length, &log_length, info_log);
158         info_log[log_length] = 0; 
159         printf("shader compile log: %s\n", info_log);
160
161         GLint status;
162         glGetShaderiv(obj, GL_COMPILE_STATUS, &status);
163         if (status == GL_FALSE) {
164                 exit(1);
165         }
166
167         return obj;
168 }
169
170 void draw_picture_quad(GLint prog, int frame)
171 {
172         glUseProgramObjectARB(prog);
173         check_error();
174
175         glActiveTexture(GL_TEXTURE0);
176         glBindTexture(GL_TEXTURE_2D, SOURCE_IMAGE);
177         glUniform1i(glGetUniformLocation(prog, "tex"), 0);
178
179         glActiveTexture(GL_TEXTURE1);
180         glBindTexture(GL_TEXTURE_1D, SRGB_LUT);
181         glUniform1i(glGetUniformLocation(prog, "srgb_tex"), 1);
182
183         glActiveTexture(GL_TEXTURE2);
184         glBindTexture(GL_TEXTURE_1D, SRGB_REVERSE_LUT);
185         glUniform1i(glGetUniformLocation(prog, "srgb_reverse_tex"), 2);
186
187         glUniform3f(glGetUniformLocation(prog, "lift"), lift_r, lift_g, lift_b);
188         //glUniform3f(glGetUniformLocation(prog, "gamma"), gamma_r, gamma_g, gamma_b);
189         glUniform3f(glGetUniformLocation(prog, "inv_gamma_22"),
190                     2.2f / gamma_r,
191                     2.2f / gamma_g,
192                     2.2f / gamma_b);
193         glUniform3f(glGetUniformLocation(prog, "gain_pow_inv_gamma"),
194                     pow(gain_r, 1.0f / gamma_r),
195                     pow(gain_g, 1.0f / gamma_g),
196                     pow(gain_b, 1.0f / gamma_b));
197         glUniform1f(glGetUniformLocation(prog, "saturation"), saturation);
198
199         glDisable(GL_BLEND);
200         check_error();
201         glDisable(GL_DEPTH_TEST);
202         check_error();
203         glDepthMask(GL_FALSE);
204         check_error();
205
206         glMatrixMode(GL_PROJECTION);
207         glLoadIdentity();
208         glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
209
210         glMatrixMode(GL_MODELVIEW);
211         glLoadIdentity();
212
213         glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
214 //      glClear(GL_COLOR_BUFFER_BIT);
215         check_error();
216
217         glBegin(GL_QUADS);
218
219         glTexCoord2f(0.0f, 1.0f);
220         glVertex2f(0.0f, 0.0f);
221
222         glTexCoord2f(1.0f, 1.0f);
223         glVertex2f(1.0f, 0.0f);
224
225         glTexCoord2f(1.0f, 0.0f);
226         glVertex2f(1.0f, 1.0f);
227
228         glTexCoord2f(0.0f, 0.0f);
229         glVertex2f(0.0f, 1.0f);
230
231         glEnd();
232         check_error();
233 }
234
235 void draw_hsv_wheel(float y, float rad, float theta, float value)
236 {
237         glUseProgramObjectARB(0);
238         check_error();
239         glActiveTexture(GL_TEXTURE0);
240         check_error();
241         glEnable(GL_TEXTURE_2D);
242         check_error();
243         glBindTexture(GL_TEXTURE_2D, HSV_WHEEL);
244         check_error();
245         glActiveTexture(GL_TEXTURE1);
246         check_error();
247         glBindTexture(GL_TEXTURE_2D, 0);
248         check_error();
249         glActiveTexture(GL_TEXTURE0);
250         glEnable(GL_BLEND);
251         check_error();
252         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
253         check_error();
254
255         // wheel
256         glBegin(GL_QUADS);
257
258         glTexCoord2f(0.0f, 1.0f);
259         glVertex2f(0.0f, y);
260
261         glTexCoord2f(1.0f, 1.0f);
262         glVertex2f(0.2f * 9.0f / 16.0f, y);
263
264         glTexCoord2f(1.0f, 0.0f);
265         glVertex2f(0.2f * 9.0f / 16.0f, y + 0.2f);
266
267         glTexCoord2f(0.0f, 0.0f);
268         glVertex2f(0.0f, y + 0.2f);
269
270         glEnd();
271
272         // wheel selector
273         glDisable(GL_TEXTURE_2D);
274         glColor3f(0.0f, 0.0f, 0.0f);
275         glPointSize(5.0f);
276         glBegin(GL_POINTS);
277         glVertex2f((0.1f + rad * cos(theta) * 0.1f) * 9.0f / 16.0f, y + 0.1f - rad * sin(theta) * 0.1f);
278         glEnd();
279         
280         // value slider
281         glDisable(GL_TEXTURE_2D);
282         glBegin(GL_QUADS);
283
284         glColor3f(0.0f, 0.0f, 0.0f);
285         glVertex2f(0.22f * 9.0f / 16.0f, y);
286         glVertex2f(0.24f * 9.0f / 16.0f, y);
287
288         glColor3f(1.0f, 1.0f, 1.0f);
289         glVertex2f(0.24f * 9.0f / 16.0f, y + 0.2f);
290         glVertex2f(0.22f * 9.0f / 16.0f, y + 0.2f);
291
292         glEnd();
293
294         // value selector
295         glColor3f(0.0f, 0.0f, 0.0f);
296         glPointSize(5.0f);
297         glBegin(GL_POINTS);
298         glVertex2f(0.23f * 9.0f / 16.0f, y + value * 0.2f);
299         glEnd();
300
301         glColor3f(1.0f, 1.0f, 1.0f);
302 }
303
304 void draw_saturation_bar(float y)
305 {
306         glUseProgramObjectARB(0);
307         check_error();
308
309         // value slider
310         glDisable(GL_TEXTURE_2D);
311         glBegin(GL_QUADS);
312
313         glColor3f(0.0f, 0.0f, 0.0f);
314         glVertex2f(0.0f * 9.0f / 16.0f, y + 0.02f);
315         glVertex2f(0.0f * 9.0f / 16.0f, y);
316
317         glColor3f(1.0f, 1.0f, 1.0f);
318         glVertex2f(0.2f * 9.0f / 16.0f, y);
319         glVertex2f(0.2f * 9.0f / 16.0f, y + 0.02f);
320
321         glEnd();
322
323         // value selector
324         glColor3f(0.0f, 0.0f, 0.0f);
325         glPointSize(5.0f);
326         glBegin(GL_POINTS);
327         glVertex2f(0.2f * (saturation / 4.0f) * 9.0f / 16.0f, y + 0.01f);
328         glEnd();
329
330         glColor3f(1.0f, 1.0f, 1.0f);
331 }
332
333 void make_hsv_wheel_texture()
334 {
335         static unsigned char hsv_pix[HSV_WHEEL_SIZE * HSV_WHEEL_SIZE * 4];
336         for (int y = 0; y < HSV_WHEEL_SIZE; ++y) {
337                 for (int x = 0; x < HSV_WHEEL_SIZE; ++x) {
338                         float yf = 2.0f * y / (float)(HSV_WHEEL_SIZE) - 1.0f;
339                         float xf = 2.0f * x / (float)(HSV_WHEEL_SIZE) - 1.0f;
340                         float rad = hypot(xf, yf);
341                         float theta = atan2(yf, xf);
342
343                         float r, g, b;
344                         hsv2rgb(theta, rad, 1.0f, &r, &g, &b);
345                         hsv_pix[(y * HSV_WHEEL_SIZE + x) * 4 + 0] = lrintf(r * 255.0f);
346                         hsv_pix[(y * HSV_WHEEL_SIZE + x) * 4 + 1] = lrintf(g * 255.0f);
347                         hsv_pix[(y * HSV_WHEEL_SIZE + x) * 4 + 2] = lrintf(b * 255.0f);
348
349                         if (rad > 1.0f) {
350                                 hsv_pix[(y * HSV_WHEEL_SIZE + x) * 4 + 3] = 0;
351                         } else {
352                                 hsv_pix[(y * HSV_WHEEL_SIZE + x) * 4 + 3] = 255;
353                         }
354                 }
355                 printf("\n");
356         }
357
358         glBindTexture(GL_TEXTURE_2D, HSV_WHEEL);
359         check_error();
360         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
361         check_error();
362         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, HSV_WHEEL_SIZE, HSV_WHEEL_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, hsv_pix);
363         check_error();
364 }
365
366 void update_hsv()
367 {
368         hsv2rgb(lift_theta, lift_rad, lift_v, &lift_r, &lift_g, &lift_b);
369         hsv2rgb(gamma_theta, gamma_rad, gamma_v * 2.0f, &gamma_r, &gamma_g, &gamma_b);
370         hsv2rgb(gain_theta, gain_rad, gain_v * 4.0f, &gain_r, &gain_g, &gain_b);
371
372         if (saturation < 0.0) {
373                 saturation = 0.0;
374         }
375
376         printf("lift: %f %f %f\n", lift_r, lift_g, lift_b);
377         printf("gamma: %f %f %f\n", gamma_r, gamma_g, gamma_b);
378         printf("gain: %f %f %f\n", gain_r, gain_g, gain_b);
379         printf("saturation: %f\n", saturation);
380         printf("\n");
381 }
382
383 void read_colorwheel(float xf, float yf, float *rad, float *theta, float *value)
384 {
385         if (xf < 0.2f && yf < 0.2f) {
386                 float xp = 2.0f * xf / 0.2f - 1.0f;
387                 float yp = -(2.0f * yf / 0.2f - 1.0f);
388                 *rad = hypot(xp, yp);
389                 *theta = atan2(yp, xp);
390                 if (*rad > 1.0) {
391                         *rad = 1.0;
392                 }
393         } else if (xf >= 0.22f && xf <= 0.24f) {
394                 *value = yf / 0.2f;
395         }
396 }
397
398 void mouse(int x, int y)
399 {
400         float xf = (x / (float)WIDTH) * 16.0f / 9.0f;
401         float yf = (HEIGHT - y) / (float)HEIGHT;
402
403         if (yf < 0.2f) {
404                 read_colorwheel(xf, yf, &lift_rad, &lift_theta, &lift_v);
405         } else if (yf >= 0.2f && yf < 0.4f) {
406                 read_colorwheel(xf, yf - 0.2f, &gamma_rad, &gamma_theta, &gamma_v);
407         } else if (yf >= 0.4f && yf < 0.6f) {
408                 read_colorwheel(xf, yf - 0.4f, &gain_rad, &gain_theta, &gain_v);
409         } else if (yf >= 0.6f && yf < 0.62f && xf < 0.2f) {
410                 saturation = (xf / 0.2f) * 4.0f;
411         }
412
413         update_hsv();
414 }
415
416 void load_texture(const char *filename)
417 {
418         SDL_Surface *img = IMG_Load(filename);
419         if (img == NULL) {
420                 fprintf(stderr, "Load of '%s' failed\n", filename);
421                 exit(1);
422         }
423
424         // Convert to RGB.
425         SDL_PixelFormat *fmt = img->format;
426         SDL_LockSurface(img);
427         unsigned char *src_pixels = (unsigned char *)img->pixels;
428         unsigned char *dst_pixels = (unsigned char *)malloc(img->w * img->h * 3);
429         for (unsigned i = 0; i < img->w * img->h; ++i) {
430                 unsigned char r, g, b;
431                 unsigned int temp;
432                 unsigned int pixel = *(unsigned int *)(src_pixels + i * fmt->BytesPerPixel);
433
434                 temp = pixel & fmt->Rmask;
435                 temp = temp >> fmt->Rshift;
436                 temp = temp << fmt->Rloss;
437                 r = temp;
438
439                 temp = pixel & fmt->Gmask;
440                 temp = temp >> fmt->Gshift;
441                 temp = temp << fmt->Gloss;
442                 g = temp;
443
444                 temp = pixel & fmt->Bmask;
445                 temp = temp >> fmt->Bshift;
446                 temp = temp << fmt->Bloss;
447                 b = temp;
448
449                 dst_pixels[i * 3 + 0] = r;
450                 dst_pixels[i * 3 + 1] = g;
451                 dst_pixels[i * 3 + 2] = b;
452         }
453         SDL_UnlockSurface(img);
454
455 #if 1
456         // we will convert to sRGB in the shader
457         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img->w, img->h, 0, GL_RGB, GL_UNSIGNED_BYTE, dst_pixels);
458         check_error();
459 #else
460         // implicit sRGB conversion in hardware
461         glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8, img->w, img->h, 0, GL_RGB, GL_UNSIGNED_BYTE, dst_pixels);
462         check_error();
463 #endif
464         free(dst_pixels);
465         SDL_FreeSurface(img);
466 }
467
468 void write_ppm(const char *filename, unsigned char *screenbuf)
469 {
470         FILE *fp = fopen(filename, "w");
471         fprintf(fp, "P6\n%d %d\n255\n", WIDTH, HEIGHT);
472         for (unsigned y = 0; y < HEIGHT; ++y) {
473                 unsigned char *srcptr = screenbuf + ((HEIGHT - y - 1) * WIDTH) * 4;
474                 for (unsigned x = 0; x < WIDTH; ++x) {
475                         fputc(srcptr[x * 4 + 2], fp);
476                         fputc(srcptr[x * 4 + 1], fp);
477                         fputc(srcptr[x * 4 + 0], fp);
478                 }
479         }
480         fclose(fp);
481 }
482
483 int main(int argc, char **argv)
484 {
485         int quit = 0;
486
487         SDL_Init(SDL_INIT_EVERYTHING);
488         SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
489         SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
490         SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
491         SDL_SetVideoMode(WIDTH, HEIGHT, 0, SDL_OPENGL);
492         SDL_WM_SetCaption("OpenGL window", NULL);
493         
494         // geez 
495         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
496         glPixelStorei(GL_PACK_ALIGNMENT, 1);
497
498         glBindTexture(GL_TEXTURE_2D, SOURCE_IMAGE);
499         check_error();
500         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
501         check_error();
502
503         load_texture("blg_wheels_woman_1.jpg");
504         //glGenerateMipmap(GL_TEXTURE_2D);
505         //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 4);
506         //check_error();
507
508         //load_texture("maserati_gts_wallpaper_1280x720_01.jpg");
509         //load_texture("90630d1295075297-console-games-wallpapers-wallpaper_need_for_speed_prostreet_09_1920x1080.jpg");
510         //load_texture("glacier-lake-1280-720-4087.jpg");
511
512 #if 0
513         // sRGB reverse LUT
514         glBindTexture(GL_TEXTURE_1D, SRGB_REVERSE_LUT);
515         check_error();
516         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
517         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
518         check_error();
519         float srgb_reverse_tex[4096];
520         for (unsigned i = 0; i < 4096; ++i) {
521                 float x = i / 4095.0;
522                 if (x < 0.0031308f) {
523                         srgb_reverse_tex[i] = 12.92f * x;
524                 } else {
525                         srgb_reverse_tex[i] = 1.055f * pow(x, 1.0f / 2.4f) - 0.055f;
526                 }
527         }
528         glTexImage1D(GL_TEXTURE_1D, 0, GL_LUMINANCE16F_ARB, 4096, 0, GL_LUMINANCE, GL_FLOAT, srgb_reverse_tex);
529         check_error();
530
531         // sRGB LUT
532         glBindTexture(GL_TEXTURE_1D, SRGB_LUT);
533         check_error();
534         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
535         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
536         check_error();
537         float srgb_tex[256];
538         for (unsigned i = 0; i < 256; ++i) {
539                 float x = i / 255.0;
540                 if (x < 0.04045f) {
541                         srgb_tex[i] = x * (1.0f / 12.92f);
542                 } else {
543                         srgb_tex[i] = pow((x + 0.055) * (1.0 / 1.055f), 2.4);
544                 }
545         }
546         glTexImage1D(GL_TEXTURE_1D, 0, GL_LUMINANCE16F_ARB, 256, 0, GL_LUMINANCE, GL_FLOAT, srgb_tex);
547         check_error();
548 #endif
549
550         // generate a PDO to hold the data we read back with glReadPixels()
551         // (Intel/DRI goes into a slow path if we don't read to PDO)
552         glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 1);
553         glBufferData(GL_PIXEL_PACK_BUFFER_ARB, WIDTH * HEIGHT * 4, NULL, GL_STREAM_READ);
554
555         make_hsv_wheel_texture();
556         update_hsv();
557
558         int prog = glCreateProgram();
559         GLhandleARB vs_obj = read_shader("vs.glsl", GL_VERTEX_SHADER);
560         GLhandleARB fs_obj = read_shader("fs.glsl", GL_FRAGMENT_SHADER);
561         glAttachObjectARB(prog, vs_obj);
562         check_error();
563         glAttachObjectARB(prog, fs_obj);
564         check_error();
565         glLinkProgram(prog);
566         check_error();
567
568         GLchar info_log[4096];
569         GLsizei log_length = sizeof(info_log) - 1;
570         log_length = sizeof(info_log) - 1;
571         glGetProgramInfoLog(prog, log_length, &log_length, info_log);
572         info_log[log_length] = 0; 
573         printf("link: %s\n", info_log);
574
575         struct timespec start, now;
576         int frame = 0, screenshot = 0;
577         clock_gettime(CLOCK_MONOTONIC, &start);
578
579         while (!quit) {
580                 SDL_Event event;
581                 while (SDL_PollEvent(&event)) {
582                         if (event.type == SDL_QUIT) {
583                                 quit = 1;
584                         } else if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
585                                 quit = 1;
586                         } else if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F1) {
587                                 screenshot = 1;
588                         } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
589                                 mouse(event.button.x, event.button.y);
590                         } else if (event.type == SDL_MOUSEMOTION && (event.motion.state & SDL_BUTTON(1))) {
591                                 mouse(event.motion.x, event.motion.y);
592                         }
593                 }
594
595                 ++frame;
596
597                 draw_picture_quad(prog, frame);
598                 
599                 glReadPixels(0, 0, WIDTH, HEIGHT, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
600                 check_error();
601
602                 draw_hsv_wheel(0.0f, lift_rad, lift_theta, lift_v);
603                 draw_hsv_wheel(0.2f, gamma_rad, gamma_theta, gamma_v);
604                 draw_hsv_wheel(0.4f, gain_rad, gain_theta, gain_v);
605                 draw_saturation_bar(0.6f);
606
607                 SDL_GL_SwapBuffers();
608                 check_error();
609
610                 unsigned char *screenbuf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
611                 check_error();
612                 if (screenshot) {
613                         char filename[256];
614                         sprintf(filename, "frame%05d.ppm", frame);
615                         write_ppm(filename, screenbuf);
616                         printf("Screenshot: %s\n", filename);
617                         screenshot = 0;
618                 }
619                 glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);
620                 check_error();
621
622 #if 1
623                 clock_gettime(CLOCK_MONOTONIC, &now);
624                 double elapsed = now.tv_sec - start.tv_sec +
625                         1e-9 * (now.tv_nsec - start.tv_nsec);
626                 printf("%d frames in %.3f seconds = %.1f fps (%.1f ms/frame)\n",
627                         frame, elapsed, frame / elapsed,
628                         1e3 * elapsed / frame);
629 #endif
630         }
631         return 0; 
632 }