summaryrefslogtreecommitdiff
path: root/glsldebayer.c
diff options
context:
space:
mode:
authordatenwolf <@datenwolf.net>2025-07-03 00:19:42 +0200
committerdatenwolf <@datenwolf.net>2025-07-03 00:19:42 +0200
commit1a9569912879f52c69a6f301719e5b543fe86cc6 (patch)
tree58fa880ad2a138f302c75a328bd6e27a89e85773 /glsldebayer.c
downloadglsldebayer-1a9569912879f52c69a6f301719e5b543fe86cc6.tar.gz
glsldebayer-1a9569912879f52c69a6f301719e5b543fe86cc6.tar.bz2
base commit, SDL2, EGL and OpenGL-ES 2 setup, texture upload and basic de-Bayer
Diffstat (limited to 'glsldebayer.c')
-rw-r--r--glsldebayer.c314
1 files changed, 314 insertions, 0 deletions
diff --git a/glsldebayer.c b/glsldebayer.c
new file mode 100644
index 0000000..1e0720e
--- /dev/null
+++ b/glsldebayer.c
@@ -0,0 +1,314 @@
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <EGL/egl.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_syswm.h>
+
+GLuint load_gl_shader_from_sources(
+ GLenum shader_unit,
+ char const * const * const sources,
+ GLint const * const lengths )
+{
+ GLuint shader = 0;
+ GLint shader_status = GL_FALSE;
+ char *shader_infolog = NULL;
+ do {
+ size_t n_sources = 0;
+ while( sources[n_sources] ){ ++n_sources; }
+
+ shader = glCreateShader(shader_unit);
+ if( !shader ){ break; }
+ glShaderSource(shader, n_sources, sources, lengths);
+ glCompileShader(shader);
+
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_status);
+ if( !shader_status ){
+ GLint log_length, returned_length;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+
+ shader_infolog = malloc(log_length);
+ if( shader_infolog ){
+ memset(shader_infolog, 0, log_length);
+ glGetShaderInfoLog(
+ shader,
+ log_length,
+ &returned_length,
+ shader_infolog );
+ char const * shader_unit_str = NULL;
+ switch(shader_unit) {
+ case GL_VERTEX_SHADER: shader_unit_str = "vertex"; break;
+ case GL_FRAGMENT_SHADER: shader_unit_str = "fragment"; break;
+ }
+ fprintf(stderr,
+ "%s shader compilation failed;\n%*s",
+ shader_unit_str,
+ returned_length, shader_infolog );
+ }
+ }
+ } while(0);
+
+
+ if( !shader_status ){ glDeleteShader(shader); shader = 0; }
+ if( shader_infolog ){ free(shader_infolog); }
+
+ return shader;
+}
+
+GLuint load_gl_program_from_sources(
+ char const * const * const sources_vs,
+ GLint const * const lengths_vs,
+ char const * const * const sources_fs,
+ GLint const * const lengths_fs )
+{
+ GLint linkStatus = GL_FALSE;
+ GLuint program = 0, vert_shader = 0, frag_shader = 0;
+ char const *msg = NULL;
+ do {
+ program = glCreateProgram();
+ if( !program ){ fprintf(stderr, "glCreateProgram(): %d", glGetError()); break; }
+
+ if( sources_vs ){
+ vert_shader = load_gl_shader_from_sources(GL_VERTEX_SHADER, sources_vs, lengths_vs);
+ if( !vert_shader ){ break; }
+ glAttachShader(program, vert_shader);
+ }
+ if( sources_fs ){
+ frag_shader = load_gl_shader_from_sources(GL_FRAGMENT_SHADER, sources_fs, lengths_fs);
+ if( !frag_shader ){ break; }
+ glAttachShader(program, frag_shader);
+ }
+
+ glLinkProgram(program);
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if( !linkStatus ){
+ GLint log_length, returned_length;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+
+ char *program_infolog = malloc(log_length);
+ if( program_infolog ){
+ glGetProgramInfoLog(program, log_length, &returned_length, program_infolog);
+ fprintf(stderr,
+ "shader program linking failed;\n%*s",
+ returned_length, program_infolog );
+ free(program_infolog);
+ }
+ }
+ } while(0);
+
+ /* shaders are retained inside the GL context until their last holder,
+ * i.e. the shader program that was just linked is deleted. */
+ if( vert_shader ){ glDeleteShader(vert_shader); }
+ if( frag_shader ){ glDeleteShader(frag_shader); }
+
+ if( !linkStatus ){ glDeleteProgram(program); program = 0; }
+
+ return program;
+}
+
+extern char const _binary_pixels_raw_start;
+extern char const _binary_pixels_raw_end;
+#define _binary_pixels_raw ((void const*const)&_binary_pixels_raw_start)
+#define _binary_pixels_raw_size (size_t)(&_binary_pixels_raw_end - &_binary_pixels_raw_start)
+#define _binary_pixels_raw_width 1280
+#define _binary_pixels_raw_height 960
+
+extern char const _binary_debayer_vs_glsl_end;
+extern char const _binary_debayer_vs_glsl_start;
+#define _binary_debayer_vs_glsl ((void const*const)&_binary_debayer_vs_glsl_start)
+#define _binary_debayer_vs_glsl_size (size_t)(&_binary_debayer_vs_glsl_end - &_binary_debayer_vs_glsl_start)
+
+extern char const _binary_debayer_fs_glsl_end;
+extern char const _binary_debayer_fs_glsl_start;
+#define _binary_debayer_fs_glsl ((void const*const)&_binary_debayer_fs_glsl_start)
+#define _binary_debayer_fs_glsl_size (size_t)(&_binary_debayer_fs_glsl_end - &_binary_debayer_fs_glsl_start)
+
+static GLenum draw_debayer_RGB(GLuint texture)
+{
+#if 1
+/* For the moment, just perform 1st degree unity mapping */
+GLfloat const coef_r[16] = {
+ 1., 0., 0., 0.,
+ 0., 0., 0., 0.,
+ 0., 0., 0., 0.,
+ 0., 0., 0., 0. };
+GLfloat const *const coef_g = coef_r;
+GLfloat const *const coef_b = coef_r;
+
+/* Those coefficients seem to be way off. Need to investigate further. */
+#elif 0
+GLfloat const coef_r[16] = {
+ 4.41898209e-01, -1.08617799e-01, -5.05377382e+00, 9.52185120e+00,
+ 2.05420594e+02, -6.17439055e+02, -2.71301881e+03, 1.26291235e+04,
+ 4.06578493e+03, -9.34105168e+04, 1.33991823e+05, 1.27240329e+05,
+ -5.67983982e+05, 6.77352564e+05, -3.70453281e+05, 7.96913561e+04 };
+
+GLfloat const coef_g[16] = {
+ 1.75630153e-01, 1.77970251e-01, 3.93921859e+00, -3.83900750e+01,
+ -8.50868580e+01, 1.39497351e+03, -1.50332837e+03, -1.85003596e+04,
+ 6.53562460e+04, -1.00909757e+04, -3.92586418e+05, 1.06909568e+06,
+ -1.42125035e+06, 1.07029531e+06, -4.38315295e+05, 7.62246706e+04 };
+
+GLfloat const coef_b[16] = {
+ 2.44126153e-01, 1.71659546e-01, 5.35993954e-01, -8.10729044e+00,
+ -7.11102399e+00, 1.24421866e+02, 1.22255723e-02, -8.74245656e+02,
+ 4.72294698e+02, 2.99248407e+03, -2.85191269e+03, -4.54081854e+03,
+ 6.46045466e+03, 1.41007972e+03, -5.12551110e+03, 1.94825904e+03 };
+#else
+GLfloat const coef_r[16] = {
+ 2.43242336e+00, 1.29527225e+01, -1.67053874e+02, -1.03264624e+03,
+ 3.89533658e+04, -3.79431617e+05, 2.04443576e+06, -7.12156509e+06,
+ 1.71198465e+07, -2.92874193e+07, 3.60398126e+07, -3.17297224e+07,
+ 1.95237028e+07, -7.98040387e+06, 1.94790465e+06, -2.14928147e+05 };
+
+GLfloat const coef_g[16] = {
+ 5.24353947e+00, 2.96584994e+01, -1.73865606e+02, -1.93045987e+03,
+ 1.85513793e+04, -4.48754651e+04, -1.32006487e+05, 1.24471739e+06,
+ -4.25234811e+06, 8.83297659e+06, -1.23501912e+07, 1.19377860e+07,
+ -7.90632536e+06, 3.43589308e+06, -8.84463954e+05, 1.02356618e+05 };
+
+GLfloat const coef_b[16] = {
+ 4.04493164e+00, -1.46245348e+01, 1.71095269e+02, 2.06498987e+03,
+ -4.93803069e+04, 3.95481675e+05, -1.82105431e+06, 5.53531911e+06,
+ -1.17786850e+07, 1.80202854e+07, -1.99760831e+07, 1.59213215e+07,
+ -8.89476364e+06, 3.30494584e+06, -7.32930586e+05, 7.33188936e+04 };
+#endif
+
+ static GLuint prog = 0, a_position, u_sampler, u_coef_r, u_coef_g, u_coef_b;
+ if( !prog ){
+ char const *src_vs[] = { _binary_debayer_vs_glsl, NULL };
+ GLint const sz_vs[] = { _binary_debayer_vs_glsl_size, 0 };
+ char const *src_fs[] = { _binary_debayer_fs_glsl, NULL };
+ GLint const sz_fs[] = { _binary_debayer_fs_glsl_size, 0 };
+ prog = load_gl_program_from_sources(src_vs, sz_vs, src_fs, sz_fs);
+ if( !prog ){ _Exit(1); return GL_INVALID_VALUE; }
+ a_position = glGetAttribLocation(prog, "a_position");
+ u_sampler = glGetUniformLocation(prog, "u_sampler");
+ u_coef_r = glGetUniformLocation(prog, "u_coef_r");
+ u_coef_g = glGetUniformLocation(prog, "u_coef_g");
+ u_coef_b = glGetUniformLocation(prog, "u_coef_b");
+ }
+
+ static GLuint vbo_xy = 0;
+ if( !vbo_xy ){
+ // a triangle that fully covers the rectangle
+ // ((0,0),(1,1)) in counter clock vertex order.
+ GLfloat const data[] = {
+ 0.0, 0.0,
+ 2.0, 0.0,
+ 0.0, 2.0
+ };
+ glGenBuffers(1, &vbo_xy);
+ if( !vbo_xy ){ return glGetError(); }
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_xy);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+
+ static GLuint tex;
+ if( !tex ){
+ glGenTextures(1, &tex);
+ if( !tex ){ return glGetError(); }
+ glBindTexture(GL_TEXTURE_2D, tex);
+ if( !(_binary_pixels_raw_width % 4) ){
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ } else
+ if( !(_binary_pixels_raw_width % 2) ){
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
+ } else {
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ }
+ /* when loading the pixels into the texture, each two consecutive
+ * primitive pixels are coalesced into a single 2-channel texel.
+ * Furthermore, since primitives order are distinct between
+ * even and odd rows, a texture half the primitive height is created
+ * but at the primitive picture width. Hence we will end up a
+ * texture that contains two pictures side by side, even rows
+ * on the left and odd rows on the right. Since the subpictures
+ * have uniform primitives arrangement each, we can use built-in
+ * mipmap generation to downsample the picture for mipmapping
+ * without impeding the de-Bayering process. */
+ glTexImage2D(
+ /* target = */ GL_TEXTURE_2D,
+ /* level = */ 0,
+ /* The 2 component image format of OpenGL-ES 2
+ * is GL_LUMINANCE_ALPHA; on later versions you'd
+ * use GL_RG; internalformat = */ GL_LUMINANCE_ALPHA,
+ /* width = */ _binary_pixels_raw_width,
+ /* height = */ _binary_pixels_raw_height/2,
+ /* border = */ 0,
+ /* format = */ GL_LUMINANCE_ALPHA,
+ /* type = */ GL_UNSIGNED_BYTE,
+ /* data = */ _binary_pixels_raw );
+ glGenerateMipmap(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ glUseProgram(prog);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glUniform1i(u_sampler, 0);
+ glUniformMatrix4fv(u_coef_r, 1, GL_FALSE, coef_r);
+ glUniformMatrix4fv(u_coef_g, 1, GL_FALSE, coef_g);
+ glUniformMatrix4fv(u_coef_b, 1, GL_FALSE, coef_b);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_xy);
+ glEnableVertexAttribArray(a_position);
+ glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glUseProgram(0);
+
+ return glGetError();
+}
+
+int main()
+{
+ int screenwidth = _binary_pixels_raw_width;
+ int screenheight = _binary_pixels_raw_height;
+
+ EGLint numConfigs, majorVersion, minorVersion;
+ SDL_Window *window = SDL_CreateWindow(
+ "GLESv2 / fragment shader de-Bayer", 0, 0,
+ screenwidth, screenheight, SDL_WINDOW_OPENGL);
+
+ EGLConfig config = {0};
+ EGLint const egl_config_attr[] = {
+ EGL_BUFFER_SIZE, 24,
+ EGL_DEPTH_SIZE, 24,
+ EGL_STENCIL_SIZE, 8,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_NONE
+ };
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ eglInitialize(display, &majorVersion, &minorVersion);
+ eglChooseConfig(display, egl_config_attr, &config, 1, &numConfigs);
+
+ SDL_SysWMinfo sysInfo;
+ SDL_VERSION(&sysInfo.version);
+ SDL_GetWindowWMInfo(window, &sysInfo);
+
+ EGLint const egl_context_attr[] = {
+ EGL_CONTEXT_MAJOR_VERSION, 2,
+ EGL_NONE
+ };
+ EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, egl_context_attr);
+ EGLSurface surface = eglCreateWindowSurface(display, config, (EGLNativeWindowType)sysInfo.info.x11.window, 0); // X11?
+ eglMakeCurrent(display, surface, surface, context);
+ eglSwapInterval(display, 1);
+
+ for( SDL_Event event = {0}
+ ; SDL_QUIT != event.type
+ ; SDL_PollEvent(&event)
+ ){
+ glViewport(0, 0, screenwidth, screenheight);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ draw_debayer_RGB(0);
+ eglSwapBuffers(display, surface);
+ }
+
+ eglDestroySurface(display, surface);
+ eglDestroyContext(display, context);
+ eglTerminate(display);
+ SDL_DestroyWindow(window);
+ return 0;
+}