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