Ver Fonte

It works!

Benjamin Schaaf há 3 anos atrás
pai
commit
337526e9b9
19 ficheiros alterados com 1241 adições e 686 exclusões
  1. 1 0
      camera.c
  2. 0 4
      camera.css
  3. 1 1
      camera.glade
  4. 335 0
      camera.ui
  5. 3 3
      config/pine64,pinephone-1.0.ini
  6. 3 3
      config/pine64,pinephone-1.1.ini
  7. 3 3
      config/pine64,pinephone-1.2.ini
  8. 11 0
      data/blit.frag
  9. 12 0
      data/blit.vert
  10. 28 21
      data/debayer.frag
  11. 20 9
      data/debayer.vert
  12. 65 133
      gl_quickpreview.c
  13. 128 0
      gl_utils.c
  14. 8 29
      gl_utils.h
  15. 385 250
      main.c
  16. 2 1
      main.h
  17. 2 1
      meson.build
  18. 226 226
      process_pipeline.c
  19. 8 2
      process_pipeline.h

+ 1 - 0
camera.c

@@ -607,6 +607,7 @@ mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer)
 			/* fallthrough */
 		default:
 			errno_printerr("VIDIOC_DQBUF");
+			exit(1);
 			return false;
 		}
 	}

+ 0 - 4
camera.css

@@ -1,7 +1,3 @@
-.black {
-    background: #000000;
-}
-
 .errorbox {
     background: #dd0000;
     color: #ffffff;

+ 1 - 1
camera.glade

@@ -27,7 +27,7 @@
                 <property name="can-focus">False</property>
                 <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkDrawingArea" id="preview">
+                  <object class="GtkGLArea" id="preview">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
                   </object>

+ 335 - 0
camera.ui

@@ -0,0 +1,335 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <object class="GtkAdjustment" id="control_adj">
+    <property name="upper">100</property>
+    <property name="step-increment">1</property>
+    <property name="page-increment">10</property>
+  </object>
+  <object class="GtkWindow" id="window">
+    <property name="can-focus">0</property>
+    <property name="default-width">360</property>
+    <property name="default-height">640</property>
+    <property name="decorated">0</property>
+    <property name="child">
+      <object class="GtkStack" id="main_stack">
+        <property name="can-focus">0</property>
+        <child>
+          <object class="GtkStackPage">
+            <property name="name">main</property>
+            <property name="child">
+              <object class="GtkBox" id="page_main">
+                <property name="can-focus">0</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkGLArea" id="preview">
+                    <property name="vexpand">1</property>
+                    <property name="can-focus">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="control_box">
+                    <property name="visible">0</property>
+                    <property name="can-focus">0</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="margin-start">10</property>
+                        <property name="margin-end">10</property>
+                        <property name="margin-top">10</property>
+                        <property name="margin-bottom">10</property>
+                        <child>
+                          <object class="GtkLabel" id="control_name">
+                            <property name="can-focus">0</property>
+                            <property name="margin-end">10</property>
+                            <property name="label" translatable="yes">ISO</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkScale" id="control_slider">
+                            <property name="hexpand">1</property>
+                            <property name="adjustment">control_adj</property>
+                            <property name="round-digits">1</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkToggleButton" id="control_auto">
+                            <property name="label" translatable="yes">Auto</property>
+                            <property name="receives-default">1</property>
+                            <property name="margin-start">10</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <style>
+                      <class name="controlbox"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkActionBar" id="controls_box">
+                    <property name="can-focus">0</property>
+                    <child type="start">
+                      <object class="GtkButton" id="settings">
+                        <property name="receives-default">1</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="can-focus">0</property>
+                            <property name="resource">/org/postmarketos/Megapixels/settings-symbolic.svg</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="start">
+                      <object class="GtkButton" id="switch_camera">
+                        <property name="receives-default">1</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="can-focus">0</property>
+                            <property name="resource">/org/postmarketos/Megapixels/switch-camera.svg</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="center">
+                      <object class="GtkButton" id="shutter">
+                        <property name="width-request">48</property>
+                        <property name="height-request">48</property>
+                        <property name="receives-default">1</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="can-focus">0</property>
+                            <property name="resource">/org/postmarketos/Megapixels/shutter-button.svg</property>
+                          </object>
+                        </child>
+                        <style>
+                          <class name="circular"/>
+                        </style>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkButton" id="open_directory">
+                        <property name="receives-default">1</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="can-focus">0</property>
+                            <property name="resource">/org/postmarketos/Megapixels/folder-symbolic.svg</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkButton" id="open_last">
+                        <property name="receives-default">1</property>
+                        <child>
+                          <object class="GtkStack" id="open_last_stack">
+                            <property name="can-focus">0</property>
+                            <child>
+                              <object class="GtkImage" id="thumb_last">
+                                <property name="width-request">24</property>
+                                <property name="height-request">24</property>
+                                <property name="can-focus">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkSpinner" id="process_spinner">
+                                <property name="width-request">24</property>
+                                <property name="height-request">24</property>
+                                <property name="can-focus">0</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="error_box">
+                    <property name="visible">0</property>
+                    <property name="can-focus">0</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="margin-start">10</property>
+                        <property name="margin-end">10</property>
+                        <property name="margin-top">10</property>
+                        <property name="margin-bottom">10</property>
+                        <property name="spacing">10</property>
+                        <child>
+                          <object class="GtkLabel" id="error_message">
+                            <property name="hexpand">1</property>
+                            <property name="can-focus">0</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">No error</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="error_close">
+                            <property name="label">gtk-close</property>
+                            <property name="receives-default">1</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <style>
+                      <class name="errorbox"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </property>
+          </object>
+        </child>
+        <!-- <child>
+          <object class="GtkStackPage">
+            <property name="name">settings</property>
+            <property name="title" translatable="yes">page1</property>
+            <property name="position">1</property>
+            <property name="child">
+              <object class="GtkScrolledWindow" id="page_settings">
+                <property name="shadow-type">in</property>
+                <property name="child">
+                  <object class="GtkViewport">
+                    <property name="can-focus">0</property>
+                    <property name="shadow-type">none</property>
+                    <property name="child">
+                      <object class="GtkBox">
+                        <property name="can-focus">0</property>
+                        <property name="margin-start">10</property>
+                        <property name="margin-end">10</property>
+                        <property name="margin-top">10</property>
+                        <property name="margin-bottom">10</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">10</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <child>
+                              <object class="GtkButton" id="settings_back">
+                                <property name="label" translatable="yes">Back</property>
+                                <property name="receives-default">1</property>
+                                <property name="margin-start">10</property>
+                                <property name="margin-end">10</property>
+                                <style>
+                                  <class name="suggested-action"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Settings aren&apos;t functional yet</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">4</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="can-focus">0</property>
+                                <property name="halign">start</property>
+                                <property name="label" translatable="yes">Photos</property>
+                                <style>
+                                  <class name="heading"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkFrame">
+                                <property name="can-focus">0</property>
+                                <property name="shadow-type">in</property>
+                                <child>
+                                  <object class="GtkAlignment">
+                                    <property name="visible">True</property>
+                                    <property name="can-focus">False</property>
+                                    <property name="left-padding">12</property>
+                                    <child>
+                                      <object class="GtkBox">
+                                        <property name="can-focus">0</property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="spacing">6</property>
+                                        <child>
+                                          <object class="GtkLabel">
+                                            <property name="can-focus">0</property>
+                                            <property name="halign">start</property>
+                                            <property name="label" translatable="yes">Resolution</property>
+                                          </object>
+                                        </child>
+                                        <child>
+                                          <object class="GtkComboBox">
+                                            <property name="can-focus">0</property>
+                                          </object>
+                                        </child>
+                                        <child>
+                                          <placeholder/>
+                                        </child>
+                                        <child>
+                                          <placeholder/>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel">
+                                            <property name="can-focus">0</property>
+                                            <property name="halign">start</property>
+                                            <property name="label" translatable="yes">Storage mode</property>
+                                          </object>
+                                        </child>
+                                        <child>
+                                          <object class="GtkBox">
+                                            <property name="can-focus">0</property>
+                                            <property name="orientation">vertical</property>
+                                            <child>
+                                              <object class="GtkCheckButton" id="store_vng">
+                                                <property name="label" translatable="yes">Debayer with VNG (slowest)</property>
+                                                <property name="active">1</property>
+                                              </object>
+                                            </child>
+                                            <child>
+                                              <object class="GtkCheckButton" id="store_simple">
+                                                <property name="label" translatable="yes">Debayer with linear interpolation</property>
+                                                <property name="group">store_vng</property>
+                                              </object>
+                                            </child>
+                                            <child>
+                                              <object class="GtkCheckButton" id="store_raw">
+                                                <property name="label" translatable="yes">Raw</property>
+                                                <property name="group">store_vng</property>
+                                              </object>
+                                            </child>
+                                          </object>
+                                        </child>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child type="label_item">
+                                  <placeholder/>
+                                </child>
+                                <style>
+                                  <class name="view"/>
+                                </style>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                    </property>
+                  </object>
+                </property>
+              </object>
+            </property>
+          </object>
+        </child> -->
+      </object>
+    </property>
+  </object>
+</interface>

+ 3 - 3
config/pine64,pinephone-1.0.ini

@@ -11,7 +11,7 @@ capture-rate=10
 capture-fmt=BGGR8
 preview-width=1280
 preview-height=720
-preview-rate=20
+preview-rate=60
 preview-fmt=BGGR8
 rotate=270
 colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
@@ -29,11 +29,11 @@ driver=gc2145
 media-driver=sun6i-csi
 capture-width=1280
 capture-height=960
-capture-rate=30
+capture-rate=60
 capture-fmt=BGGR8
 preview-width=1280
 preview-height=960
-preview-rate=30
+preview-rate=60
 preview-fmt=BGGR8
 rotate=90
 mirrored=true

+ 3 - 3
config/pine64,pinephone-1.1.ini

@@ -11,7 +11,7 @@ capture-rate=10
 capture-fmt=BGGR8
 preview-width=1280
 preview-height=720
-preview-rate=20
+preview-rate=60
 preview-fmt=BGGR8
 rotate=270
 colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
@@ -29,11 +29,11 @@ driver=gc2145
 media-driver=sun6i-csi
 capture-width=1280
 capture-height=960
-capture-rate=30
+capture-rate=60
 capture-fmt=BGGR8
 preview-width=1280
 preview-height=960
-preview-rate=30
+preview-rate=60
 preview-fmt=BGGR8
 rotate=90
 mirrored=true

+ 3 - 3
config/pine64,pinephone-1.2.ini

@@ -11,7 +11,7 @@ capture-rate=10
 capture-fmt=BGGR8
 preview-width=1280
 preview-height=720
-preview-rate=20
+preview-rate=60
 preview-fmt=BGGR8
 rotate=270
 mirrored=false
@@ -30,11 +30,11 @@ driver=gc2145
 media-driver=sun6i-csi
 capture-width=1280
 capture-height=960
-capture-rate=30
+capture-rate=60
 capture-fmt=BGGR8
 preview-width=1280
 preview-height=960
-preview-rate=30
+preview-rate=60
 preview-fmt=BGGR8
 rotate=90
 mirrored=true

+ 11 - 0
data/blit.frag

@@ -0,0 +1,11 @@
+precision mediump float;
+
+uniform sampler2D texture;
+
+varying vec2 uv;
+
+#define fetch(p) texture2D(texture, p).r
+
+void main() {
+	gl_FragColor = texture2D(texture, uv);
+}

+ 12 - 0
data/blit.vert

@@ -0,0 +1,12 @@
+precision mediump float;
+
+attribute vec2 vert;
+attribute vec2 tex_coord;
+
+varying vec2 uv;
+
+void main() {
+	uv = tex_coord;
+
+	gl_Position = vec4(vert, 0, 1);
+}

+ 28 - 21
data/debayer.frag

@@ -1,36 +1,43 @@
-precision mediump float;
+precision highp float;
 
 uniform sampler2D texture;
-// uniform sampler2D srgb_map;
+uniform sampler2D pixel_layout;
 uniform mat3 color_matrix;
 
-varying vec2 uv1;
-varying vec2 uv2;
-varying vec2 pixel_coord;
+uniform vec2 pixel_size;
+
+// varying vec2 uv1;
+// varying vec2 uv2;
+varying vec2 top_left_uv;
+varying vec2 top_right_uv;
+varying vec2 bottom_left_uv;
+varying vec2 bottom_right_uv;
+varying vec2 half_pixel_coord;
 
 #define fetch(p) texture2D(texture, p).r
 
 void main() {
 	vec4 pixels = vec4(
-		fetch(uv1),
-		fetch(vec2(uv1.x, uv2.y)),
-		fetch(uv2),
-		fetch(vec2(uv2.x, uv1.y)));
+		fetch(top_left_uv),
+		fetch(top_right_uv),
+		fetch(bottom_right_uv),
+		fetch(bottom_left_uv));
+
+	vec2 arrangement = floor(half_pixel_coord);
 
-	vec2 arrangement = mod(pixel_coord, 2.0);
+	vec2 is_bottom_left = step(1.0, arrangement);
 
-	vec2 is_top_left = step(1.0, arrangement);
+	// vec3 color = mix(
+	// 	mix(pixels.xyz, pixels.yzw, is_bottom_left.y),
+	// 	mix(pixels.wzy, pixels.zyx, is_bottom_left.y),
+	// 	is_bottom_left.x);
+	vec3 color = pixels.zyx;
 
-	vec3 color = mix(
-		mix(pixels.zyx, pixels.wzy, is_top_left.y),
-		mix(pixels.yzw, pixels.xyz, is_top_left.y),
-		is_top_left.x);
+	// Fast SRGB estimate. See https://mimosa-pudica.net/fast-gamma/
+	vec3 srgb_color = (vec3(1.138) * inversesqrt(color) - vec3(0.138)) * color;
 
-	vec3 srgb_color = pow(color, vec3(1.0 / 2.2));
-	// vec3 srgb_color = vec3(
-	// 	texture2D(srgb_map, vec2(color.r, 0)).r,
-	// 	texture2D(srgb_map, vec2(color.g, 0)).r,
-	// 	texture2D(srgb_map, vec2(color.b, 0)).r);
+	// Slow SRGB estimate
+	// vec3 srgb_color = pow(color, vec3(1.0 / 2.2));
 
-	gl_FragColor = vec4((color_matrix * srgb_color).bgr, 0);
+	gl_FragColor = vec4(color_matrix * srgb_color, 0);
 }

+ 20 - 9
data/debayer.vert

@@ -1,21 +1,32 @@
-precision mediump float;
+precision highp float;
 
 attribute vec2 vert;
 attribute vec2 tex_coord;
 
 uniform mat3 transform;
 uniform vec2 pixel_size;
+uniform vec2 half_image_size;
 
-varying vec2 uv1;
-varying vec2 uv2;
-varying vec2 pixel_coord;
+// varying vec2 uv1;
+// varying vec2 uv2;
+
+varying vec2 top_left_uv;
+varying vec2 top_right_uv;
+varying vec2 bottom_left_uv;
+varying vec2 bottom_right_uv;
+varying vec2 half_pixel_coord;
 
 void main() {
-	uv1 = tex_coord - pixel_size / 2.0;
-	uv2 = tex_coord + pixel_size / 2.0;
-	uv1 += pixel_size;
-	uv2 += pixel_size;
-	pixel_coord = uv1 / pixel_size;
+	top_left_uv = tex_coord - pixel_size / 2.0;
+	bottom_right_uv = tex_coord + pixel_size / 2.0;
+	top_right_uv = vec2(top_left_uv.x, bottom_right_uv.y);
+	bottom_left_uv = vec2(bottom_right_uv.x, top_left_uv.y);
+
+	// uv1 = tex_coord - pixel_size / 2.0;
+	// uv2 = tex_coord + pixel_size / 2.0;
+	// uv1 += pixel_size;
+	// uv2 += pixel_size;
+	half_pixel_coord = top_left_uv * half_image_size;
 
 	gl_Position = vec4(transform * vec3(vert, 1), 1);
 }

+ 65 - 133
gl_quickpreview.c

@@ -1,101 +1,28 @@
 #include "camera.h"
 #include "gl_quickpreview.h"
 #include "gl_utils.h"
-#include <stdint.h>
 #include <stdlib.h>
 
+
 #define VERTEX_ATTRIBUTE 0
 #define TEX_COORD_ATTRIBUTE 1
 
-static const GLfloat square_vertices[] = {
-	-1, -1,
-	1, -1,
-	-1, 1,
-	1, 1,
-};
-
-static const GLfloat square_texcoords[] = {
-	1, 1,
-	1, 0,
-	0, 1,
-	0, 0,
-};
-
 struct _GLQuickPreview {
 	GLuint frame_buffer;
 	GLuint program;
 	GLuint uniform_transform;
 	GLuint uniform_pixel_size;
+	GLuint uniform_half_image_size;
 	GLuint uniform_texture;
+	GLuint uniform_pixel_layout;
 	GLuint uniform_srgb_map;
 	GLuint uniform_color_matrix;
-	GLuint srgb_texture;
-};
-
-static GLuint load_shader(const char *path, GLenum type)
-{
-	check_gl();
-
-	FILE *f = fopen(path, "r");
-
-	fseek(f, 0, SEEK_END);
-	GLint size = ftell(f);
-	char *source = malloc(sizeof(char) * size);
-
-	fseek(f, 0, SEEK_SET);
-	if (fread(source, size, 1, f) == 0) {
-		printf("Failed to read shader file\n");
-		return 0;
-	}
 
-	GLuint shader = glCreateShader(type);
-	assert(shader != 0);
-	glShaderSource(shader, 1, (const GLchar * const*)&source, &size);
-	glCompileShader(shader);
-	check_gl();
-
-	// Check compile status
-	GLint success;
-	glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
-	if (success == GL_FALSE) {
-		printf("Shader compilation failed for %s\n", path);
-	}
-
-	GLint log_length;
-	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
-	if (log_length > 0) {
-		char *log = malloc(sizeof(char) * log_length);
-		glGetShaderInfoLog(shader, log_length - 1, &log_length, log);
-
-		printf("Shader %s log: %s\n", path, log);
-		free(log);
-	}
-
-	free(source);
-
-	return shader;
-}
-
-// static const uint8_t srgb[] = {
-// 	0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70,
-// 	73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100,
-// 	102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118,
-// 	120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134,
-// 	135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147,
-// 	148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159,
-// 	160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170,
-// 	171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180,
-// 	181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189,
-// 	190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198,
-// 	199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206,
-// 	207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214,
-// 	215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222,
-// 	222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229,
-// 	230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236,
-// 	237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243,
-// 	243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249,
-// 	250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255
-// };
+	GLuint pixel_layout_texture_id;
+	uint32_t pixel_layout_texture_width;
+	uint32_t pixel_layout_texture_height;
+	MPPixelFormat pixel_layout_texture_format;
+};
 
 GLQuickPreview *gl_quick_preview_new()
 {
@@ -103,51 +30,22 @@ GLQuickPreview *gl_quick_preview_new()
 	glGenFramebuffers(1, &frame_buffer);
 	check_gl();
 
-	GLuint vert = load_shader("data/debayer.vert", GL_VERTEX_SHADER);
-	GLuint frag = load_shader("data/debayer.frag", GL_FRAGMENT_SHADER);
-
-	GLuint program = glCreateProgram();
-	glAttachShader(program, vert);
-	glAttachShader(program, frag);
+	GLuint shaders[] = {
+		gl_load_shader("data/debayer.vert", GL_VERTEX_SHADER),
+		gl_load_shader("data/debayer.frag", GL_FRAGMENT_SHADER),
+	};
 
+	GLuint program = gl_link_program(shaders, 2);
 	glBindAttribLocation(program, VERTEX_ATTRIBUTE, "vert");
 	glBindAttribLocation(program, TEX_COORD_ATTRIBUTE, "tex_coord");
-	glLinkProgram(program);
-	check_gl();
-
-	GLint success;
-	glGetProgramiv(program, GL_LINK_STATUS, &success);
-	if (success == GL_FALSE) {
-		printf("Program linking failed\n");
-	}
-
-	GLint log_length;
-	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
-	if (log_length > 0) {
-		char *log = malloc(sizeof(char) * log_length);
-		glGetProgramInfoLog(program, log_length - 1, &log_length, log);
-
-		printf("Program log: %s\n", log);
-		free(log);
-	}
 	check_gl();
 
-	// GLuint srgb_texture;
-	// glGenTextures(1, &srgb_texture);
-	// check_gl();
-
 	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 	check_gl();
 
-	// glBindTexture(GL_TEXTURE_2D, srgb_texture);
-	// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-	// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-	// check_gl();
-	// glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 256, 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, srgb);
-	// check_gl();
-	// glBindTexture(GL_TEXTURE_2D, 0);
+	GLuint pixel_layout_texture_id;
+	glGenTextures(1, &pixel_layout_texture_id);
+	check_gl();
 
 	GLQuickPreview *self = malloc(sizeof(GLQuickPreview));
 	self->frame_buffer = frame_buffer;
@@ -155,10 +53,18 @@ GLQuickPreview *gl_quick_preview_new()
 
 	self->uniform_transform = glGetUniformLocation(self->program, "transform");
 	self->uniform_pixel_size = glGetUniformLocation(self->program, "pixel_size");
+	self->uniform_half_image_size = glGetUniformLocation(self->program, "half_image_size");
 	self->uniform_texture = glGetUniformLocation(self->program, "texture");
+	self->uniform_pixel_layout = glGetUniformLocation(self->program, "pixel_layout");
 	self->uniform_color_matrix = glGetUniformLocation(self->program, "color_matrix");
 	self->uniform_srgb_map = glGetUniformLocation(self->program, "srgb_map");
-	// self->srgb_texture = srgb_texture;
+	check_gl();
+
+	self->pixel_layout_texture_id = pixel_layout_texture_id;
+	self->pixel_layout_texture_width = 0;
+	self->pixel_layout_texture_height = 0;
+	self->pixel_layout_texture_format = MP_PIXEL_FMT_BGGR8;
+
 	return self;
 }
 
@@ -187,6 +93,36 @@ gl_quick_preview(GLQuickPreview *self,
 		 const float *colormatrix,
 		 const uint8_t blacklevel)
 {
+	// Generate pixel layout image
+	if (src_width != self->pixel_layout_texture_width
+	    || src_height != self->pixel_layout_texture_height
+	    || format != self->pixel_layout_texture_format) {
+
+		glBindTexture(GL_TEXTURE_2D, self->pixel_layout_texture_id);
+
+		uint16_t *buffer = malloc(src_width * src_height * sizeof(uint16_t));
+		for (size_t y = 0; y < src_height; ++y) {
+			for (size_t x = 0; x < src_width; ++x) {
+				buffer[x + y * src_width] =
+					(y % 2) == 0
+					? ((x % 2) == 0 ? 0b1111 << 4 : 0b1111 << 8)
+					: ((x % 2) == 0 ? 0b1111 << 8 : 0b1111 << 12);
+			}
+		}
+
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		glTexImage2D(GL_TEXTURE_2D, 0,  GL_RGBA, src_width, src_height, 0,  GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, buffer);
+		check_gl();
+		free(buffer);
+
+		self->pixel_layout_texture_width = src_width;
+		self->pixel_layout_texture_height = src_height;
+		self->pixel_layout_texture_format = format;
+		glBindTexture(GL_TEXTURE_2D, 0);
+		check_gl();
+	}
+
 	assert(ql_quick_preview_supports_format(self, format));
 
 	glBindFramebuffer(GL_FRAMEBUFFER, self->frame_buffer);
@@ -202,7 +138,7 @@ gl_quick_preview(GLQuickPreview *self,
 	glUseProgram(self->program);
 	check_gl();
 
-	GLfloat rotation_list[4] = { 1, 0, -1, 0 };
+	GLfloat rotation_list[4] = { 0, -1, 0, 1 };
 	int rotation_index = 4 - rotation / 90;
 
 	GLfloat sin_rot = rotation_list[rotation_index];
@@ -216,6 +152,9 @@ gl_quick_preview(GLQuickPreview *self,
 	glUniformMatrix3fv(self->uniform_transform, 1, GL_FALSE, matrix);
 	check_gl();
 
+	glUniform2f(self->uniform_half_image_size, src_width / 2, src_height / 2);
+	check_gl();
+
 	GLfloat pixel_size_x = 1.0f / src_width;
 	GLfloat pixel_size_y = 1.0f / src_height;
 	glUniform2f(self->uniform_pixel_size, pixel_size_x, pixel_size_y);
@@ -226,10 +165,10 @@ gl_quick_preview(GLQuickPreview *self,
 	glUniform1i(self->uniform_texture, 0);
 	check_gl();
 
-	// glActiveTexture(GL_TEXTURE1);
-	// glBindTexture(GL_TEXTURE_2D, self->srgb_texture);
-	// glUniform1i(self->uniform_srgb_map, 1);
-	// check_gl();
+	glActiveTexture(GL_TEXTURE1);
+	glBindTexture(GL_TEXTURE_2D, self->pixel_layout_texture_id);
+	glUniform1i(self->uniform_pixel_layout, 1);
+	check_gl();
 
 	if (colormatrix)
 	{
@@ -250,14 +189,10 @@ gl_quick_preview(GLQuickPreview *self,
 		glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, identity);
 	}
 
-	glVertexAttribPointer(VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, square_vertices);
-	check_gl();
+	glVertexAttribPointer(VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, gl_quad_vertices);
 	glEnableVertexAttribArray(VERTEX_ATTRIBUTE);
-	check_gl();
-	glVertexAttribPointer(TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, square_texcoords);
-	check_gl();
+	glVertexAttribPointer(TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, gl_quad_texcoords);
 	glEnableVertexAttribArray(TEX_COORD_ATTRIBUTE);
-	check_gl();
 	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 	check_gl();
 
@@ -268,8 +203,5 @@ gl_quick_preview(GLQuickPreview *self,
 	// glBindTexture(GL_TEXTURE_2D, 0);
 	// glBindFramebuffer(GL_FRAMEBUFFER, 0);
 
-	glUseProgram(0);
-	check_gl();
-
 	return true;
 }

+ 128 - 0
gl_utils.c

@@ -0,0 +1,128 @@
+#include "gl_utils.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+void __check_gl(const char *file, int line)
+{
+	GLenum error = glGetError();
+
+	const char *name;
+	switch (error) {
+		case GL_NO_ERROR:
+			return; // no error
+		case GL_INVALID_ENUM:
+			name = "GL_INVALID_ENUM";
+			break;
+		case GL_INVALID_VALUE:
+			name = "GL_INVALID_VALUE";
+			break;
+		case GL_INVALID_OPERATION:
+			name = "GL_INVALID_OPERATION";
+			break;
+		case GL_INVALID_FRAMEBUFFER_OPERATION:
+			name = "GL_INVALID_FRAMEBUFFER_OPERATION";
+			break;
+		case GL_OUT_OF_MEMORY:
+			name = "GL_OUT_OF_MEMORY";
+			break;
+		default:
+			name = "UNKNOWN ERROR!";
+			break;
+	}
+
+	printf("GL error at %s:%d - %s\n", file, line, name);
+}
+
+const GLfloat gl_quad_vertices[] = {
+	-1, -1,
+	1, -1,
+	-1, 1,
+	1, 1,
+};
+
+const GLfloat gl_quad_texcoords[] = {
+	0, 0,
+	1, 0,
+	0, 1,
+	1, 1,
+};
+
+GLuint
+gl_load_shader(const char *path, GLenum type)
+{
+	check_gl();
+
+	FILE *f = fopen(path, "r");
+
+	fseek(f, 0, SEEK_END);
+	GLint size = ftell(f);
+	char *source = malloc(sizeof(char) * size);
+
+	fseek(f, 0, SEEK_SET);
+	if (fread(source, size, 1, f) == 0) {
+		printf("Failed to read shader file\n");
+		return 0;
+	}
+
+	GLuint shader = glCreateShader(type);
+	assert(shader != 0);
+	glShaderSource(shader, 1, (const GLchar * const*)&source, &size);
+	glCompileShader(shader);
+	check_gl();
+
+	// Check compile status
+	GLint success;
+	glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+	if (success == GL_FALSE) {
+		printf("Shader compilation failed for %s\n", path);
+	}
+
+	GLint log_length;
+	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+	if (log_length > 0) {
+		char *log = malloc(sizeof(char) * log_length);
+		glGetShaderInfoLog(shader, log_length - 1, &log_length, log);
+
+		printf("Shader %s log: %s\n", path, log);
+		free(log);
+	}
+
+	free(source);
+
+	return shader;
+}
+
+GLuint
+gl_link_program(GLuint *shaders, size_t num_shaders)
+{
+	GLuint program = glCreateProgram();
+
+	for (size_t i = 0; i < num_shaders; ++i) {
+		glAttachShader(program, shaders[i]);
+	}
+
+	glLinkProgram(program);
+	check_gl();
+
+	GLint success;
+	glGetProgramiv(program, GL_LINK_STATUS, &success);
+	if (success == GL_FALSE) {
+		printf("Program linking failed\n");
+	}
+
+	GLint log_length;
+	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+	if (log_length > 0) {
+		char *log = malloc(sizeof(char) * log_length);
+		glGetProgramInfoLog(program, log_length - 1, &log_length, log);
+
+		printf("Program log: %s\n", log);
+		free(log);
+	}
+	check_gl();
+
+	return program;
+}

+ 8 - 29
gl_utils.h

@@ -1,34 +1,13 @@
+#pragma once
+
 #include <GLES2/gl2.h>
+#include <stddef.h>
 
 #define check_gl() __check_gl(__FILE__, __LINE__)
+void __check_gl(const char *file, int line);
 
-static void __check_gl(const char *file, int line)
-{
-	GLenum error = glGetError();
-
-	const char *name;
-	switch (error) {
-		case GL_NO_ERROR:
-			return; // no error
-		case GL_INVALID_ENUM:
-			name = "GL_INVALID_ENUM";
-			break;
-		case GL_INVALID_VALUE:
-			name = "GL_INVALID_VALUE";
-			break;
-		case GL_INVALID_OPERATION:
-			name = "GL_INVALID_OPERATION";
-			break;
-		case GL_INVALID_FRAMEBUFFER_OPERATION:
-			name = "GL_INVALID_FRAMEBUFFER_OPERATION";
-			break;
-		case GL_OUT_OF_MEMORY:
-			name = "GL_OUT_OF_MEMORY";
-			break;
-		default:
-			name = "UNKNOWN ERROR!";
-			break;
-	}
+extern const GLfloat gl_quad_vertices[8];
+extern const GLfloat gl_quad_texcoords[8];
 
-	printf("GL error at %s:%d - %s\n", file, line, name);
-}
+GLuint gl_load_shader(const char *path, GLenum type);
+GLuint gl_link_program(GLuint *shaders, size_t num_shaders);

+ 385 - 250
main.c

@@ -18,11 +18,20 @@
 #include <gtk/gtk.h>
 #include <locale.h>
 #include <zbar.h>
+#include "gl_utils.h"
 #include "camera_config.h"
 #include "quickpreview.h"
 #include "io_pipeline.h"
 #include "process_pipeline.h"
 
+#define RENDERDOC
+
+#ifdef RENDERDOC
+#include <dlfcn.h>
+#include <renderdoc/app.h>
+RENDERDOC_API_1_1_2 *rdoc_api = NULL;
+#endif
+
 enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
 
 static bool camera_is_initialized = false;
@@ -42,7 +51,8 @@ static int exposure;
 static bool has_auto_focus_continuous;
 static bool has_auto_focus_start;
 
-static cairo_surface_t *surface = NULL;
+static MPProcessPipelineBuffer *current_preview_buffer = NULL;
+
 static cairo_surface_t *status_surface = NULL;
 static char last_path[260] = "";
 
@@ -157,21 +167,21 @@ void mp_main_set_zbar_result(MPZBarScanResult *result)
 }
 
 static bool
-set_preview(cairo_surface_t *image)
+set_preview(MPProcessPipelineBuffer *buffer)
 {
-	if (surface) {
-		cairo_surface_destroy(surface);
+	if (current_preview_buffer) {
+		mp_process_pipeline_buffer_unref(current_preview_buffer);
 	}
-	surface = image;
+	current_preview_buffer = buffer;
 	gtk_widget_queue_draw(preview);
 	return false;
 }
 
 void
-mp_main_set_preview(cairo_surface_t *image)
+mp_main_set_preview(MPProcessPipelineBuffer *buffer)
 {
 	g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
-				   (GSourceFunc)set_preview, image, NULL);
+				   (GSourceFunc)set_preview, buffer, NULL);
 }
 
 static void transform_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
@@ -210,7 +220,7 @@ capture_completed(struct capture_completed_args *args)
 {
 	strncpy(last_path, args->fname, 259);
 
-	gtk_image_set_from_surface(GTK_IMAGE(thumb_last), args->thumb);
+	// gtk_image_set_from_surface(GTK_IMAGE(thumb_last), args->thumb);
 
 	gtk_spinner_stop(GTK_SPINNER(process_spinner));
 	gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
@@ -234,7 +244,7 @@ mp_main_capture_completed(cairo_surface_t *thumb, const char *fname)
 static void
 draw_controls()
 {
-	cairo_t *cr;
+	// cairo_t *cr;
 	char iso[6];
 	int temp;
 	char shutterangle[6];
@@ -259,72 +269,124 @@ draw_controls()
 		cairo_surface_destroy(status_surface);
 
 	// Make a service to show status of controls, 32px high
-	if (gtk_widget_get_window(preview) == NULL) {
+	// if (gtk_widget_get_window(preview) == NULL) {
+	// 	return;
+	// }
+	// status_surface =
+	// 	gdk_window_create_similar_surface(gtk_widget_get_window(preview),
+	// 					  CAIRO_CONTENT_COLOR_ALPHA,
+	// 					  preview_width, 32);
+
+	// cr = cairo_create(status_surface);
+	// cairo_set_source_rgba(cr, 0, 0, 0, 0.0);
+	// cairo_paint(cr);
+
+	// // Draw the outlines for the headings
+	// cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
+	// 		       CAIRO_FONT_WEIGHT_BOLD);
+	// cairo_set_font_size(cr, 9);
+	// cairo_set_source_rgba(cr, 0, 0, 0, 1);
+
+	// cairo_move_to(cr, 16, 16);
+	// cairo_text_path(cr, "ISO");
+	// cairo_stroke(cr);
+
+	// cairo_move_to(cr, 60, 16);
+	// cairo_text_path(cr, "Shutter");
+	// cairo_stroke(cr);
+
+	// // Draw the fill for the headings
+	// cairo_set_source_rgba(cr, 1, 1, 1, 1);
+	// cairo_move_to(cr, 16, 16);
+	// cairo_show_text(cr, "ISO");
+	// cairo_move_to(cr, 60, 16);
+	// cairo_show_text(cr, "Shutter");
+
+	// // Draw the outlines for the values
+	// cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
+	// 		       CAIRO_FONT_WEIGHT_NORMAL);
+	// cairo_set_font_size(cr, 11);
+	// cairo_set_source_rgba(cr, 0, 0, 0, 1);
+
+	// cairo_move_to(cr, 16, 26);
+	// cairo_text_path(cr, iso);
+	// cairo_stroke(cr);
+
+	// cairo_move_to(cr, 60, 26);
+	// cairo_text_path(cr, shutterangle);
+	// cairo_stroke(cr);
+
+	// // Draw the fill for the values
+	// cairo_set_source_rgba(cr, 1, 1, 1, 1);
+	// cairo_move_to(cr, 16, 26);
+	// cairo_show_text(cr, iso);
+	// cairo_move_to(cr, 60, 26);
+	// cairo_show_text(cr, shutterangle);
+
+	// cairo_destroy(cr);
+
+	// gtk_widget_queue_draw_area(preview, 0, 0, preview_width, 32);
+}
+
+static GLuint blit_program;
+static GLuint blit_uniform_texture;
+
+static void
+preview_realize(GtkGLArea *area)
+{
+	gtk_gl_area_make_current(area);
+
+	if (gtk_gl_area_get_error(area) != NULL) {
 		return;
 	}
-	status_surface =
-		gdk_window_create_similar_surface(gtk_widget_get_window(preview),
-						  CAIRO_CONTENT_COLOR_ALPHA,
-						  preview_width, 32);
 
-	cr = cairo_create(status_surface);
-	cairo_set_source_rgba(cr, 0, 0, 0, 0.0);
-	cairo_paint(cr);
+	GLuint blit_shaders[] = {
+		gl_load_shader("data/blit.vert", GL_VERTEX_SHADER),
+		gl_load_shader("data/blit.frag", GL_FRAGMENT_SHADER),
+	};
+
+	blit_program = gl_link_program(blit_shaders, 2);
+	glBindAttribLocation(blit_program, 0, "vert");
+	glBindAttribLocation(blit_program, 1, "tex_coord");
+	check_gl();
 
-	// Draw the outlines for the headings
-	cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
-			       CAIRO_FONT_WEIGHT_BOLD);
-	cairo_set_font_size(cr, 9);
-	cairo_set_source_rgba(cr, 0, 0, 0, 1);
-
-	cairo_move_to(cr, 16, 16);
-	cairo_text_path(cr, "ISO");
-	cairo_stroke(cr);
-
-	cairo_move_to(cr, 60, 16);
-	cairo_text_path(cr, "Shutter");
-	cairo_stroke(cr);
-
-	// Draw the fill for the headings
-	cairo_set_source_rgba(cr, 1, 1, 1, 1);
-	cairo_move_to(cr, 16, 16);
-	cairo_show_text(cr, "ISO");
-	cairo_move_to(cr, 60, 16);
-	cairo_show_text(cr, "Shutter");
-
-	// Draw the outlines for the values
-	cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
-			       CAIRO_FONT_WEIGHT_NORMAL);
-	cairo_set_font_size(cr, 11);
-	cairo_set_source_rgba(cr, 0, 0, 0, 1);
-
-	cairo_move_to(cr, 16, 26);
-	cairo_text_path(cr, iso);
-	cairo_stroke(cr);
-
-	cairo_move_to(cr, 60, 26);
-	cairo_text_path(cr, shutterangle);
-	cairo_stroke(cr);
-
-	// Draw the fill for the values
-	cairo_set_source_rgba(cr, 1, 1, 1, 1);
-	cairo_move_to(cr, 16, 26);
-	cairo_show_text(cr, iso);
-	cairo_move_to(cr, 60, 26);
-	cairo_show_text(cr, shutterangle);
-
-	cairo_destroy(cr);
-
-	gtk_widget_queue_draw_area(preview, 0, 0, preview_width, 32);
+	blit_uniform_texture = glGetUniformLocation(blit_program, "texture");
 }
 
 static gboolean
-preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
+preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data)
 {
+	if (gtk_gl_area_get_error(area) != NULL) {
+		return FALSE;
+	}
+
 	if (!camera_is_initialized) {
 		return FALSE;
 	}
 
+// #ifdef RENDERDOC
+// 	if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
+// #endif
+
+	glClearColor(0, 0, 0, 1);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	if (current_preview_buffer) {
+		glUseProgram(blit_program);
+
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_2D, mp_process_pipeline_buffer_get_texture_id(current_preview_buffer));
+		glUniform1i(blit_uniform_texture, 0);
+
+		glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, gl_quad_vertices);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, gl_quad_texcoords);
+		glEnableVertexAttribArray(1);
+		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+		check_gl();
+	}
+
+	/*
 	// Clear preview area with black
 	cairo_paint(cr);
 
@@ -367,19 +429,23 @@ preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
 	// Draw control overlay
 	cairo_set_source_surface(cr, status_surface, 0, 0);
 	cairo_paint(cr);
+	*/
+
+	glFlush();
+
+// #ifdef RENDERDOC
+// 	if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
+// #endif
+
 	return FALSE;
 }
 
 static gboolean
-preview_configure(GtkWidget *widget, GdkEventConfigure *event)
+preview_resize(GtkWidget *widget, int width, int height, gpointer data)
 {
-	int new_preview_width = gtk_widget_get_allocated_width(widget);
-	int new_preview_height = gtk_widget_get_allocated_height(widget);
-
-	if (preview_width != new_preview_width ||
-	    preview_height != new_preview_height) {
-		preview_width = new_preview_width;
-		preview_height = new_preview_height;
+	if (preview_width != width || preview_height != height) {
+		preview_width = width;
+		preview_height = height;
 		update_io_pipeline();
 	}
 
@@ -422,158 +488,158 @@ on_shutter_clicked(GtkWidget *widget, gpointer user_data)
 	mp_io_pipeline_capture();
 }
 
-void
-on_capture_shortcut(void)
-{
-	on_shutter_clicked(NULL, NULL);
-}
-
-static bool
-check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
-{
-	bool right = false, left = false, top = false, bottom = false;
-
-	for (int i = 0; i < 4; ++i) {
-		if (x <= bounds_x[i])
-			left = true;
-		if (x >= bounds_x[i])
-			right = true;
-		if (y <= bounds_y[i])
-			top = true;
-		if (y >= bounds_y[i])
-			bottom = true;
-	}
-
-	return right && left && top && bottom;
-}
-
-static void
-on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
-{
-	GtkWidget *dialog;
-	GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
-	bool data_is_url = g_uri_is_valid(
-		code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
-
-	char* data = strdup(code->data);
-
-	if (data_is_url) {
-		dialog = gtk_message_dialog_new(
-			GTK_WINDOW(gtk_widget_get_toplevel(widget)),
-			flags,
-			GTK_MESSAGE_QUESTION,
-			GTK_BUTTONS_NONE,
-			"Found a URL '%s' encoded in a %s code.",
-			code->data,
-			code->type);
-		gtk_dialog_add_buttons(
-			GTK_DIALOG(dialog),
-			"_Open URL",
-			GTK_RESPONSE_YES,
-			NULL);
-	} else {
-		dialog = gtk_message_dialog_new(
-			GTK_WINDOW(gtk_widget_get_toplevel(widget)),
-			flags,
-			GTK_MESSAGE_QUESTION,
-			GTK_BUTTONS_NONE,
-			"Found '%s' encoded in a %s code.",
-			code->data,
-			code->type);
-	}
-	gtk_dialog_add_buttons(
-		GTK_DIALOG(dialog),
-		"_Copy",
-		GTK_RESPONSE_ACCEPT,
-		"_Cancel",
-		GTK_RESPONSE_CANCEL,
-		NULL);
-
-	int result = gtk_dialog_run(GTK_DIALOG(dialog));
-
-	GError *error = NULL;
-	switch (result) {
-		case GTK_RESPONSE_YES:
-			if (!g_app_info_launch_default_for_uri(data,
-							       NULL, &error)) {
-				g_printerr("Could not launch application: %s\n",
-					   error->message);
-			}
-		case GTK_RESPONSE_ACCEPT:
-			gtk_clipboard_set_text(
-				gtk_clipboard_get(GDK_SELECTION_PRIMARY),
-				data, -1);
-		case GTK_RESPONSE_CANCEL:
-			break;
-		default:
-			g_printerr("Wrong dialog result: %d\n", result);
-	}
-	gtk_widget_destroy(dialog);
-}
-
-void
-on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
-{
-	if (event->type != GDK_BUTTON_PRESS)
-		return;
-
-	// Handle taps on the controls
-	if (event->y < 32) {
-		if (gtk_widget_is_visible(control_box)) {
-			gtk_widget_hide(control_box);
-			return;
-		} else {
-			gtk_widget_show(control_box);
-		}
-
-		if (event->x < 60) {
-			// ISO
-			current_control = USER_CONTROL_ISO;
-			gtk_label_set_text(GTK_LABEL(control_name), "ISO");
-			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
-						     !gain_is_manual);
-			gtk_adjustment_set_lower(control_slider, 0.0);
-			gtk_adjustment_set_upper(control_slider, (float)gain_max);
-			gtk_adjustment_set_value(control_slider, (double)gain);
-
-		} else if (event->x > 60 && event->x < 120) {
-			// Shutter angle
-			current_control = USER_CONTROL_SHUTTER;
-			gtk_label_set_text(GTK_LABEL(control_name), "Shutter");
-			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
-						     !exposure_is_manual);
-			gtk_adjustment_set_lower(control_slider, 1.0);
-			gtk_adjustment_set_upper(control_slider, 360.0);
-			gtk_adjustment_set_value(control_slider, (double)exposure);
-		}
-
-		return;
-	}
-
-	// Tapped zbar result
-	if (zbar_result) {
-		// Transform the event coordinates to the image
-		int width = cairo_image_surface_get_width(surface);
-		int height = cairo_image_surface_get_height(surface);
-		double scale = MIN(preview_width / (double)width, preview_height / (double)height);
-		int x = (event->x - preview_width / 2) / scale + width / 2;
-		int y = (event->y - preview_height / 2) / scale + height / 2;
-
-		for (uint8_t i = 0; i < zbar_result->size; ++i) {
-			MPZBarCode *code = &zbar_result->codes[i];
-
-			if (check_point_inside_bounds(x, y, code->bounds_x, code->bounds_y)) {
-				on_zbar_code_tapped(widget, code);
-				return;
-			}
-		}
-	}
-
-	// Tapped preview image itself, try focussing
-	if (has_auto_focus_start) {
-		mp_io_pipeline_focus();
-	}
-}
+// void
+// on_capture_shortcut(void)
+// {
+// 	on_shutter_clicked(NULL, NULL);
+// }
+
+// static bool
+// check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
+// {
+// 	bool right = false, left = false, top = false, bottom = false;
+
+// 	for (int i = 0; i < 4; ++i) {
+// 		if (x <= bounds_x[i])
+// 			left = true;
+// 		if (x >= bounds_x[i])
+// 			right = true;
+// 		if (y <= bounds_y[i])
+// 			top = true;
+// 		if (y >= bounds_y[i])
+// 			bottom = true;
+// 	}
+
+// 	return right && left && top && bottom;
+// }
+
+// static void
+// on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
+// {
+// 	GtkWidget *dialog;
+// 	GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
+// 	bool data_is_url = g_uri_is_valid(
+// 		code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
+
+// 	char* data = strdup(code->data);
+
+// 	if (data_is_url) {
+// 		dialog = gtk_message_dialog_new(
+// 			GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+// 			flags,
+// 			GTK_MESSAGE_QUESTION,
+// 			GTK_BUTTONS_NONE,
+// 			"Found a URL '%s' encoded in a %s code.",
+// 			code->data,
+// 			code->type);
+// 		gtk_dialog_add_buttons(
+// 			GTK_DIALOG(dialog),
+// 			"_Open URL",
+// 			GTK_RESPONSE_YES,
+// 			NULL);
+// 	} else {
+// 		dialog = gtk_message_dialog_new(
+// 			GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+// 			flags,
+// 			GTK_MESSAGE_QUESTION,
+// 			GTK_BUTTONS_NONE,
+// 			"Found '%s' encoded in a %s code.",
+// 			code->data,
+// 			code->type);
+// 	}
+// 	gtk_dialog_add_buttons(
+// 		GTK_DIALOG(dialog),
+// 		"_Copy",
+// 		GTK_RESPONSE_ACCEPT,
+// 		"_Cancel",
+// 		GTK_RESPONSE_CANCEL,
+// 		NULL);
+
+// 	int result = gtk_dialog_run(GTK_DIALOG(dialog));
+
+// 	GError *error = NULL;
+// 	switch (result) {
+// 		case GTK_RESPONSE_YES:
+// 			if (!g_app_info_launch_default_for_uri(data,
+// 							       NULL, &error)) {
+// 				g_printerr("Could not launch application: %s\n",
+// 					   error->message);
+// 			}
+// 		case GTK_RESPONSE_ACCEPT:
+// 			gtk_clipboard_set_text(
+// 				gtk_clipboard_get(GDK_SELECTION_PRIMARY),
+// 				data, -1);
+// 		case GTK_RESPONSE_CANCEL:
+// 			break;
+// 		default:
+// 			g_printerr("Wrong dialog result: %d\n", result);
+// 	}
+// 	gtk_widget_destroy(dialog);
+// }
+
+// void
+// on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+// {
+// 	if (event->type != GDK_BUTTON_PRESS)
+// 		return;
+
+// 	// Handle taps on the controls
+// 	if (event->y < 32) {
+// 		if (gtk_widget_is_visible(control_box)) {
+// 			gtk_widget_hide(control_box);
+// 			return;
+// 		} else {
+// 			gtk_widget_show(control_box);
+// 		}
+
+// 		if (event->x < 60) {
+// 			// ISO
+// 			current_control = USER_CONTROL_ISO;
+// 			gtk_label_set_text(GTK_LABEL(control_name), "ISO");
+// 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
+// 						     !gain_is_manual);
+// 			gtk_adjustment_set_lower(control_slider, 0.0);
+// 			gtk_adjustment_set_upper(control_slider, (float)gain_max);
+// 			gtk_adjustment_set_value(control_slider, (double)gain);
+
+// 		} else if (event->x > 60 && event->x < 120) {
+// 			// Shutter angle
+// 			current_control = USER_CONTROL_SHUTTER;
+// 			gtk_label_set_text(GTK_LABEL(control_name), "Shutter");
+// 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
+// 						     !exposure_is_manual);
+// 			gtk_adjustment_set_lower(control_slider, 1.0);
+// 			gtk_adjustment_set_upper(control_slider, 360.0);
+// 			gtk_adjustment_set_value(control_slider, (double)exposure);
+// 		}
+
+// 		return;
+// 	}
+
+// 	// Tapped zbar result
+// 	if (zbar_result) {
+// 		// Transform the event coordinates to the image
+// 		int width = cairo_image_surface_get_width(surface);
+// 		int height = cairo_image_surface_get_height(surface);
+// 		double scale = MIN(preview_width / (double)width, preview_height / (double)height);
+// 		int x = (event->x - preview_width / 2) / scale + width / 2;
+// 		int y = (event->y - preview_height / 2) / scale + height / 2;
+
+// 		for (uint8_t i = 0; i < zbar_result->size; ++i) {
+// 			MPZBarCode *code = &zbar_result->codes[i];
+
+// 			if (check_point_inside_bounds(x, y, code->bounds_x, code->bounds_y)) {
+// 				on_zbar_code_tapped(widget, code);
+// 				return;
+// 			}
+// 		}
+// 	}
+
+// 	// Tapped preview image itself, try focussing
+// 	if (has_auto_focus_start) {
+// 		mp_io_pipeline_focus();
+// 	}
+// }
 
 void
 on_error_close_clicked(GtkWidget *widget, gpointer user_data)
@@ -690,22 +756,35 @@ on_control_slider_changed(GtkAdjustment *widget, gpointer user_data)
 static void
 on_realize(GtkWidget *window, gpointer *data)
 {
-	mp_process_pipeline_init_gl(gtk_widget_get_window(window));
+	GtkNative *native = gtk_widget_get_native(window);
+	mp_process_pipeline_init_gl(gtk_native_get_surface(native));
 }
 
-int
-main(int argc, char *argv[])
+typedef struct
 {
-	if (!mp_load_config())
-		return 1;
+	GtkApplication parent_instance;
+} MegapixelsApp;
 
-	setenv("LC_NUMERIC", "C", 1);
+typedef GtkApplicationClass MegapixelsAppClass;
+
+GType megapixels_app_get_type (void);
+G_DEFINE_TYPE(MegapixelsApp, megapixels_app, GTK_TYPE_APPLICATION)
+
+static void
+startup(GApplication *app)
+{
+	G_APPLICATION_CLASS(megapixels_app_parent_class)->startup(app);
 
-	gtk_init(&argc, &argv);
 	g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
 		     TRUE, NULL);
-	GtkBuilder *builder = gtk_builder_new_from_resource(
-		"/org/postmarketos/Megapixels/camera.glade");
+
+	GtkBuilder *builder;
+	if (access("camera.ui", F_OK) != -1) {
+		builder = gtk_builder_new_from_file("camera.ui");
+	} else {
+		builder = gtk_builder_new_from_file(
+			"/org/postmarketos/Megapixels/camera.ui");
+	}
 
 	GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
 	GtkWidget *shutter = GTK_WIDGET(gtk_builder_get_object(builder, "shutter"));
@@ -734,7 +813,7 @@ main(int argc, char *argv[])
 		GTK_ADJUSTMENT(gtk_builder_get_object(builder, "control_adj"));
 	control_auto = GTK_WIDGET(gtk_builder_get_object(builder, "control_auto"));
 	g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL);
-	g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+	// g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
 	g_signal_connect(shutter, "clicked", G_CALLBACK(on_shutter_clicked), NULL);
 	g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked),
 			 NULL);
@@ -748,14 +827,15 @@ main(int argc, char *argv[])
 			 NULL);
 	g_signal_connect(open_directory, "clicked",
 			 G_CALLBACK(on_open_directory_clicked), NULL);
-	g_signal_connect(preview, "draw", G_CALLBACK(preview_draw), NULL);
-	g_signal_connect(preview, "configure-event", G_CALLBACK(preview_configure),
-			 NULL);
-	gtk_widget_set_events(preview, gtk_widget_get_events(preview) |
-					       GDK_BUTTON_PRESS_MASK |
-					       GDK_POINTER_MOTION_MASK);
-	g_signal_connect(preview, "button-press-event", G_CALLBACK(on_preview_tap),
+	g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL);
+	g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL);
+	g_signal_connect(preview, "resize", G_CALLBACK(preview_resize),
 			 NULL);
+	// gtk_widget_set_events(preview, gtk_widget_get_events(preview) |
+	// 				       GDK_BUTTON_PRESS_MASK |
+	// 				       GDK_POINTER_MOTION_MASK);
+	// g_signal_connect(preview, "button-press-event", G_CALLBACK(on_preview_tap),
+	// 		 NULL);
 	g_signal_connect(control_auto, "toggled",
 			 G_CALLBACK(on_control_auto_toggled), NULL);
 	g_signal_connect(control_slider, "value-changed",
@@ -763,7 +843,7 @@ main(int argc, char *argv[])
 
 	GtkCssProvider *provider = gtk_css_provider_new();
 	if (access("camera.css", F_OK) != -1) {
-		gtk_css_provider_load_from_path(provider, "camera.css", NULL);
+		gtk_css_provider_load_from_path(provider, "camera.css");
 	} else {
 		gtk_css_provider_load_from_resource(
 			provider, "/org/postmarketos/Megapixels/camera.css");
@@ -775,26 +855,81 @@ main(int argc, char *argv[])
 	gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
 				       GTK_STYLE_PROVIDER_PRIORITY_USER);
 
-	GClosure* capture_shortcut = g_cclosure_new(on_capture_shortcut, 0, 0);
+	// GClosure* capture_shortcut = g_cclosure_new(on_capture_shortcut, 0, 0);
 
-	GtkAccelGroup* accel_group = gtk_accel_group_new();
-	gtk_accel_group_connect(accel_group,
-			GDK_KEY_space,
-			0,
-			0,
-			capture_shortcut);
+	// GtkAccelGroup* accel_group = gtk_accel_group_new();
+	// gtk_accel_group_connect(accel_group,
+	// 		GDK_KEY_space,
+	// 		0,
+	// 		0,
+	// 		capture_shortcut);
 
-	gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
+	// gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
 
 	mp_io_pipeline_start();
 
 	camera = mp_get_camera_config(0);
 	update_io_pipeline();
 
+	gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(window));
 	gtk_widget_show(window);
-	gtk_main();
+}
 
+static void
+shutdown(GApplication *app)
+{
+	// Only do cleanup in development, let the OS clean up otherwise
+#ifdef DEBUG
 	mp_io_pipeline_stop();
+#endif
+
+	G_APPLICATION_CLASS(megapixels_app_parent_class)->shutdown(app);
+}
+
+static void
+megapixels_app_init(MegapixelsApp *app)
+{
+}
+
+static void
+megapixels_app_class_init(MegapixelsAppClass *class)
+{
+	GApplicationClass *application_class = G_APPLICATION_CLASS(class);
+
+	application_class->startup = startup;
+	application_class->shutdown = shutdown;
+}
+
+int
+main(int argc, char *argv[])
+{
+#ifdef RENDERDOC
+	{
+		void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
+		if (mod)
+		{
+		    pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
+		    int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api);
+		    assert(ret == 1);
+		}
+		else
+		{
+			printf("Renderdoc not found\n");
+		}
+	}
+#endif
+
+	if (!mp_load_config())
+		return 1;
+
+	setenv("LC_NUMERIC", "C", 1);
+
+	MegapixelsApp *app = g_object_new(
+		megapixels_app_get_type(),
+		"application-id", "org.postmarketos.Megapixels",
+		NULL);
+
+	g_application_run(G_APPLICATION(app), argc, argv);
 
 	return 0;
 }

+ 2 - 1
main.h

@@ -2,6 +2,7 @@
 
 #include "camera_config.h"
 #include "zbar_pipeline.h"
+#include "process_pipeline.h"
 #include "gtk/gtk.h"
 
 #define MP_MAIN_THUMB_SIZE 24
@@ -23,7 +24,7 @@ struct mp_main_state {
 
 void mp_main_update_state(const struct mp_main_state *state);
 
-void mp_main_set_preview(cairo_surface_t *image);
+void mp_main_set_preview(MPProcessPipelineBuffer *buffer);
 void mp_main_capture_completed(cairo_surface_t *thumb, const char *fname);
 
 void mp_main_set_zbar_result(MPZBarScanResult *result);

+ 2 - 1
meson.build

@@ -1,6 +1,6 @@
 project('megapixels', 'c')
 gnome = import('gnome')
-gtkdep = dependency('gtk+-3.0')
+gtkdep = dependency('gtk4')
 tiff = dependency('libtiff-4')
 zbar = dependency('zbar')
 threads = dependency('threads')
@@ -34,6 +34,7 @@ executable('megapixels',
            'ini.c',
            'quickpreview.c',
            'gl_quickpreview.c',
+           'gl_utils.c',
            'camera.c',
            'device.c',
            'pipeline.c',

+ 226 - 226
process_pipeline.c

@@ -16,7 +16,7 @@
 #include "gl_utils.h"
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
-#include <gdk/gdkwayland.h>
+// #include <gdk/gdkwayland.h>
 #include <sys/mman.h>
 #include <drm/drm_fourcc.h>
 
@@ -151,130 +151,100 @@ mp_process_pipeline_sync()
 
 #define NUM_BUFFERS 4
 
-static GLQuickPreview *gl_quick_preview_state = NULL;
-
-static EGLDisplay egl_display = EGL_NO_DISPLAY;
-static EGLContext egl_context = EGL_NO_CONTEXT;
+struct _MPProcessPipelineBuffer {
+	GLuint texture_id;
 
-// struct buffer {
-// 	GLuint texture_id;
-// 	EGLImage egl_image;
-// 	int dma_fd;
-// 	int dma_stride;
-// 	int dma_offset;
-// 	void *data;
-// };
-// static struct buffer input_buffers[NUM_BUFFERS];
-// static struct buffer output_buffers[NUM_BUFFERS];
+	_Atomic(int) refcount;
+};
+static MPProcessPipelineBuffer output_buffers[NUM_BUFFERS];
 
-static PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;
-static PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC eglExportDMABUFImageQueryMESA;
+static int output_buffer_width = 0;
+static int output_buffer_height = 0;
 
-static const char *
-egl_get_error_str()
+void
+mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf)
 {
-	EGLint error = eglGetError();
-	switch (error) {
-		case EGL_SUCCESS: return "EGL_SUCCESS";
-		case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
-		case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS";
-		case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC";
-		case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE";
-		case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT";
-		case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG";
-		case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
-		case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY";
-		case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE";
-		case EGL_BAD_MATCH: return "EGL_BAD_MATCH";
-		case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER";
-		case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP";
-		case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW";
-		case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST";
-	}
-	return "Unknown";
+	++buf->refcount;
 }
 
-#define RENDERDOC
-
-#ifdef RENDERDOC
-#include <dlfcn.h>
-#include <renderdoc/app.h>
-RENDERDOC_API_1_1_2 *rdoc_api = NULL;
-#endif
+void
+mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf)
+{
+	--buf->refcount;
+}
 
-static void
-init_gl(MPPipeline *pipeline, GdkWindow **window)
+uint32_t
+mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf)
 {
-#ifdef RENDERDOC
-	{
-		void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
-		if (mod)
-		{
-		    pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
-		    int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api);
-		    assert(ret == 1);
-		}
-		else
-		{
-			printf("Renderdoc not found\n");
-		}
-	}
-#endif
+	return buf->texture_id;
+}
 
-	GdkDisplay *gdk_display = gdk_window_get_display(*window);
-	egl_display = eglGetDisplay((EGLNativeDisplayType) gdk_wayland_display_get_wl_display(gdk_display));
-	assert(egl_display != EGL_NO_DISPLAY);
+static GLQuickPreview *gl_quick_preview_state = NULL;
 
-	EGLint major, minor;
-	if (!eglInitialize(egl_display, &major, &minor)) {
-		printf("Failed to initialize egl: %s\n", egl_get_error_str());
-		return;
-	}
+static GdkGLContext *context;
 
-	if (!eglBindAPI(EGL_OPENGL_ES_API)) {
-		printf("Failed to bind OpenGL ES: %s\n", egl_get_error_str());
-		return;
-	}
+static PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA;
+static PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC eglExportDMABUFImageQueryMESA;
 
-	printf("extensions: %s\n", eglQueryString(egl_display, EGL_EXTENSIONS));
+// static const char *
+// egl_get_error_str()
+// {
+// 	EGLint error = eglGetError();
+// 	switch (error) {
+// 		case EGL_SUCCESS: return "EGL_SUCCESS";
+// 		case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
+// 		case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS";
+// 		case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC";
+// 		case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE";
+// 		case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT";
+// 		case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG";
+// 		case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
+// 		case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY";
+// 		case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE";
+// 		case EGL_BAD_MATCH: return "EGL_BAD_MATCH";
+// 		case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER";
+// 		case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP";
+// 		case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW";
+// 		case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST";
+// 	}
+// 	return "Unknown";
+// }
 
-	EGLint config_attrs[1] = {
-		EGL_NONE
-	};
+#define RENDERDOC
 
-	EGLConfig config;
-	EGLint num_configs = 0;
-	if (!eglChooseConfig(egl_display, config_attrs, &config, 1, &num_configs)) {
-		printf("Failed to pick a egl config: %s\n", egl_get_error_str());
-		return;
-	}
+#ifdef RENDERDOC
+#include <renderdoc/app.h>
+extern RENDERDOC_API_1_1_2 *rdoc_api;
+#endif
 
-	if (num_configs != 1) {
-		printf("No egl configs found: %s\n", egl_get_error_str());
+static void
+init_gl(MPPipeline *pipeline, GdkSurface **surface)
+{
+	GError *error = NULL;
+	context = gdk_surface_create_gl_context(*surface, &error);
+	if (context == NULL) {
+		printf("Failed to initialize OpenGL context: %s\n", error->message);
+		g_clear_error(&error);
 		return;
 	}
 
-	EGLint context_attrs[5] = {
-		EGL_CONTEXT_CLIENT_VERSION,
-		2,
-		EGL_CONTEXT_FLAGS_KHR,
-		EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
-		EGL_NONE,
-	};
-
-	egl_context = eglCreateContext(egl_display, config, NULL, context_attrs);
-
-	if (egl_context == EGL_NO_CONTEXT) {
-		printf("Failed to create OpenGL ES context: %s\n", egl_get_error_str());
-		return;
-	}
+	gdk_gl_context_set_use_es(context, true);
+	gdk_gl_context_set_required_version(context, 2, 0);
+#ifdef DEBUG
+	gdk_gl_context_set_debug_enabled(context, true);
+#else
+	gdk_gl_context_set_debug_enabled(context, false);
+#endif
 
-	if (!eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context))
-	{
-		printf("Failed to set current OpenGL context: %s\n", egl_get_error_str());
+	gdk_gl_context_realize(context, &error);
+	if (error != NULL) {
+		printf("Failed to create OpenGL context: %s\n", error->message);
+		g_clear_object(&context);
+		g_clear_error(&error);
 		return;
 	}
 
+	gdk_gl_context_make_current(context);
 	check_gl();
 
 	eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC)
@@ -300,153 +270,154 @@ init_gl(MPPipeline *pipeline, GdkWindow **window)
 	gl_quick_preview_state = gl_quick_preview_new();
 	check_gl();
 
+	for (size_t i = 0; i < NUM_BUFFERS; ++i) {
+		glGenTextures(1, &output_buffers[i].texture_id);
+		glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	}
+
+	glBindTexture(GL_TEXTURE_2D, 0);
+
 	printf("Initialized OpenGL\n");
 }
 
 void
-mp_process_pipeline_init_gl(GdkWindow *window)
+mp_process_pipeline_init_gl(GdkSurface *surface)
 {
-	mp_pipeline_invoke(pipeline, (MPPipelineCallback) init_gl, &window, sizeof(GdkWindow *));
+	mp_pipeline_invoke(pipeline, (MPPipelineCallback) init_gl, &surface, sizeof(GdkSurface *));
 }
 
 static cairo_surface_t *
 process_image_for_preview(const uint8_t *image)
 {
-	cairo_surface_t *surface;
-
 	clock_t t1 = clock();
 
-	if (gl_quick_preview_state && ql_quick_preview_supports_format(gl_quick_preview_state, mode.pixel_format)) {
+	assert(gl_quick_preview_state && ql_quick_preview_supports_format(gl_quick_preview_state, mode.pixel_format));
+
+	// Pick an available buffer
+	MPProcessPipelineBuffer *output_buffer = NULL;
+	for (size_t i = 0; i < NUM_BUFFERS; ++i) {
+		if (output_buffers[i].refcount == 0) {
+			output_buffer = &output_buffers[i];
+		}
+	}
+
+	if (output_buffer == NULL) {
+		return NULL;
+	}
+	assert(output_buffer != NULL);
+
 #ifdef RENDERDOC
-		if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
+	if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
 #endif
-		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-		check_gl();
+	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+	check_gl();
 
-		GLuint textures[2];
-		glGenTextures(2, textures);
+	// Copy image to a GL texture. TODO: This can be avoided
+	GLuint input_texture;
+	glGenTextures(1, &input_texture);
+	glBindTexture(GL_TEXTURE_2D, input_texture);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	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, GL_LUMINANCE, mode.width, mode.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
+	check_gl();
 
-		glBindTexture(GL_TEXTURE_2D, textures[0]);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-		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, GL_LUMINANCE, mode.width, mode.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
-		check_gl();
 
-		glBindTexture(GL_TEXTURE_2D, textures[1]);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, preview_width, preview_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-		check_gl();
-
-		gl_quick_preview(
-			gl_quick_preview_state,
-			textures[1], preview_width, preview_height,
-			textures[0], mode.width, mode.height,
-			mode.pixel_format,
-			camera->rotate, camera->mirrored,
-			camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
-			camera->blacklevel);
-		check_gl();
-
-		surface = cairo_image_surface_create(
-			CAIRO_FORMAT_RGB24, preview_width, preview_height);
-		uint32_t *pixels = (uint32_t *)cairo_image_surface_get_data(surface);
-		glFinish();
-
-		clock_t t2 = clock();
-		printf("%fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000);
-
-		// {
-		// 	glBindTexture(GL_TEXTURE_2D, textures[1]);
-		// 	EGLImage egl_image = eglCreateImage(egl_display, egl_context, EGL_GL_TEXTURE_2D, (EGLClientBuffer)(size_t)textures[1], NULL);
-
-		// 	// Make sure it's in the expected format
-		// 	int fourcc;
-		// 	eglExportDMABUFImageQueryMESA(egl_display, egl_image, &fourcc, NULL, NULL);
-		// 	assert(fourcc == DRM_FORMAT_ABGR8888);
-
-
-		// 	int dmabuf_fd;
-		// 	int stride, offset;
-		// 	eglExportDMABUFImageMESA(egl_display, egl_image, &dmabuf_fd, &stride, &offset);
-
-		// 	int fsize = lseek(dmabuf_fd, 0, SEEK_END);
-		// 	printf("SIZE %d STRIDE %d OFFSET %d SIZE %d:%d\n", fsize, stride, offset, preview_width, preview_height);
-
-		// 	size_t size = stride * preview_height;
-		// 	uint32_t *data = mmap(NULL, fsize, PROT_READ, MAP_SHARED, dmabuf_fd, 0);
-		// 	assert(data != MAP_FAILED);
-
-		// 	int pixel_stride = stride / 4;
-
-		// 	for (size_t y = 0; y < preview_height; ++y) {
-		// 		for (size_t x = 0; x < preview_width; ++x) {
-		// 			uint32_t p = data[x + y * pixel_stride];
-		// 			pixels[x + y * preview_width] = p;
-		// 		// 	uint16_t p = data[x + y * stride];
-		// 		// 	uint32_t r = (p & 0b11111);
-		// 		// 	uint32_t g = ((p >> 5) & 0b11111);
-		// 		// 	uint32_t b = ((p >> 10) & 0b11111);
-		// 		// 	pixels[x + y * preview_width] = (r << 16) | (g << 8) | b;
-		// 		}
-		// 		// memcpy(pixels + preview_width * y, data + stride * y, preview_width * sizeof(uint32_t));
-		// 	}
-
-		// 	{
-		// 		FILE *f = fopen("test.raw", "w");
-		// 		fwrite(data, fsize, 1, f);
-		// 		fclose(f);
-		// 	}
-
-		// 	// memcpy(pixels, data, size);
-		// 	munmap(data, size);
-		// 	close(dmabuf_fd);
-		// }
-		glReadPixels(0, 0, preview_width, preview_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
-		check_gl();
-
-		glBindTexture(GL_TEXTURE_2D, 0);
-		glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	gl_quick_preview(
+		gl_quick_preview_state,
+		output_buffer->texture_id, output_buffer_width, output_buffer_height,
+		input_texture, mode.width, mode.height,
+		mode.pixel_format,
+		camera->rotate, camera->mirrored,
+		camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
+		camera->blacklevel);
+	check_gl();
+
+	// surface = cairo_image_surface_create(
+	// 	CAIRO_FORMAT_RGB24, preview_width, preview_height);
+	// uint32_t *pixels = (uint32_t *)cairo_image_surface_get_data(surface);
+	glFinish();
+
+	glBindTexture(GL_TEXTURE_2D, 0);
+	glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+	clock_t t2 = clock();
+	printf("%fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000);
+
+	// {
+	// 	glBindTexture(GL_TEXTURE_2D, textures[1]);
+	// 	EGLImage egl_image = eglCreateImage(egl_display, egl_context, EGL_GL_TEXTURE_2D, (EGLClientBuffer)(size_t)textures[1], NULL);
+
+	// 	// Make sure it's in the expected format
+	// 	int fourcc;
+	// 	eglExportDMABUFImageQueryMESA(egl_display, egl_image, &fourcc, NULL, NULL);
+	// 	assert(fourcc == DRM_FORMAT_ABGR8888);
+
+
+	// 	int dmabuf_fd;
+	// 	int stride, offset;
+	// 	eglExportDMABUFImageMESA(egl_display, egl_image, &dmabuf_fd, &stride, &offset);
+
+	// 	int fsize = lseek(dmabuf_fd, 0, SEEK_END);
+	// 	printf("SIZE %d STRIDE %d OFFSET %d SIZE %d:%d\n", fsize, stride, offset, preview_width, preview_height);
+
+	// 	size_t size = stride * preview_height;
+	// 	uint32_t *data = mmap(NULL, fsize, PROT_READ, MAP_SHARED, dmabuf_fd, 0);
+	// 	assert(data != MAP_FAILED);
+
+	// 	int pixel_stride = stride / 4;
+
+	// 	for (size_t y = 0; y < preview_height; ++y) {
+	// 		for (size_t x = 0; x < preview_width; ++x) {
+	// 			uint32_t p = data[x + y * pixel_stride];
+	// 			pixels[x + y * preview_width] = p;
+	// 		// 	uint16_t p = data[x + y * stride];
+	// 		// 	uint32_t r = (p & 0b11111);
+	// 		// 	uint32_t g = ((p >> 5) & 0b11111);
+	// 		// 	uint32_t b = ((p >> 10) & 0b11111);
+	// 		// 	pixels[x + y * preview_width] = (r << 16) | (g << 8) | b;
+	// 		}
+	// 		// memcpy(pixels + preview_width * y, data + stride * y, preview_width * sizeof(uint32_t));
+	// 	}
+
+	// 	{
+	// 		FILE *f = fopen("test.raw", "w");
+	// 		fwrite(data, fsize, 1, f);
+	// 		fclose(f);
+	// 	}
+
+	// 	// memcpy(pixels, data, size);
+	// 	munmap(data, size);
+	// 	close(dmabuf_fd);
+	// }
+	// glReadPixels(0, 0, preview_width, preview_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+	// check_gl();
 
 #ifdef RENDERDOC
-		if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
+	if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
 #endif
-	} else {
-		uint32_t surface_width, surface_height, skip;
-		quick_preview_size(&surface_width, &surface_height, &skip, preview_width,
-				   preview_height, mode.width, mode.height,
-				   mode.pixel_format, camera->rotate);
-
-		surface = cairo_image_surface_create(
-			CAIRO_FORMAT_RGB24, surface_width, surface_height);
 
-		uint8_t *pixels = cairo_image_surface_get_data(surface);
-
-		quick_preview((uint32_t *)pixels, surface_width, surface_height, image,
-			      mode.width, mode.height, mode.pixel_format,
-			      camera->rotate, camera->mirrored,
-			      camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
-			      camera->blacklevel, skip);
-	}
+	mp_process_pipeline_buffer_ref(output_buffer);
+	mp_main_set_preview(output_buffer);
 
 	// Create a thumbnail from the preview for the last capture
 	cairo_surface_t *thumb = NULL;
-	if (captures_remaining == 1) {
-		printf("Making thumbnail\n");
-		thumb = cairo_image_surface_create(
-			CAIRO_FORMAT_ARGB32, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE);
-
-		cairo_t *cr = cairo_create(thumb);
-		draw_surface_scaled_centered(
-			cr, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE, surface);
-		cairo_destroy(cr);
-	}
+	// if (captures_remaining == 1) {
+	// 	printf("Making thumbnail\n");
+	// 	thumb = cairo_image_surface_create(
+	// 		CAIRO_FORMAT_ARGB32, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE);
+
+	// 	cairo_t *cr = cairo_create(thumb);
+	// 	draw_surface_scaled_centered(
+	// 		cr, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE, surface);
+	// 	cairo_destroy(cr);
+	// }
 
 	// Pass processed preview to main and zbar
-	mp_zbar_pipeline_process_image(cairo_surface_reference(surface));
-	mp_main_set_preview(surface);
+	// mp_zbar_pipeline_process_image(cairo_surface_reference(surface));
 
 	return thumb;
 }
@@ -755,17 +726,39 @@ mp_process_pipeline_capture()
 	mp_pipeline_invoke(pipeline, capture, NULL, 0);
 }
 
+static void
+update_output_buffers()
+{
+	output_buffer_width = mode.width / 2;
+	output_buffer_height = mode.height / 2;
+
+	if (camera->rotate != 0 || camera->rotate != 180) {
+		int tmp = output_buffer_width;
+		output_buffer_width = output_buffer_height;
+		output_buffer_height = tmp;
+	}
+
+	for (size_t i = 0; i < NUM_BUFFERS; ++i) {
+		glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, output_buffer_width, output_buffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+	}
+
+	glBindTexture(GL_TEXTURE_2D, 0);
+}
+
 static void
 update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state)
 {
+	const bool buffer_update_required = (!mp_camera_mode_is_equivalent(&mode, &state->mode) || preview_width != state->preview_width || preview_height != state->preview_height);
+
 	camera = state->camera;
 	mode = state->mode;
 
-	burst_length = state->burst_length;
-
 	preview_width = state->preview_width;
 	preview_height = state->preview_height;
 
+	burst_length = state->burst_length;
+
 	// gain_is_manual = state->gain_is_manual;
 	gain = state->gain;
 	gain_max = state->gain_max;
@@ -773,6 +766,10 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state
 	exposure_is_manual = state->exposure_is_manual;
 	exposure = state->exposure;
 
+	if (buffer_update_required) {
+		update_output_buffers();
+	}
+
 	struct mp_main_state main_state = {
 		.camera = camera,
 		.mode = mode,
@@ -793,3 +790,6 @@ mp_process_pipeline_update_state(const struct mp_process_pipeline_state *new_sta
 	mp_pipeline_invoke(pipeline, (MPPipelineCallback)update_state, new_state,
 			   sizeof(struct mp_process_pipeline_state));
 }
+
+// FUCK YOU GTK
+void pango_fc_font_get_languages() {}

+ 8 - 2
process_pipeline.h

@@ -2,7 +2,7 @@
 
 #include "camera_config.h"
 
-typedef struct _GdkWindow GdkWindow;
+typedef struct _GdkSurface GdkSurface;
 
 struct mp_process_pipeline_state {
 	const struct mp_camera_config *camera;
@@ -28,8 +28,14 @@ void mp_process_pipeline_start();
 void mp_process_pipeline_stop();
 void mp_process_pipeline_sync();
 
-void mp_process_pipeline_init_gl(GdkWindow *window);
+void mp_process_pipeline_init_gl(GdkSurface *window);
 
 void mp_process_pipeline_process_image(MPBuffer buffer);
 void mp_process_pipeline_capture();
 void mp_process_pipeline_update_state(const struct mp_process_pipeline_state *state);
+
+typedef struct _MPProcessPipelineBuffer MPProcessPipelineBuffer;
+
+void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf);
+void mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf);
+uint32_t mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf);