From 1a9569912879f52c69a6f301719e5b543fe86cc6 Mon Sep 17 00:00:00 2001 From: datenwolf <@datenwolf.net> Date: Thu, 3 Jul 2025 00:19:42 +0200 Subject: base commit, SDL2, EGL and OpenGL-ES 2 setup, texture upload and basic de-Bayer --- .gitignore | 2 + Makefile | 19 ++++ debayer.fs.glsl | 41 ++++++++ debayer.vs.glsl | 6 ++ glsldebayer.c | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ linpoly.py | 90 ++++++++++++++++ pixels.png | Bin 0 -> 855257 bytes 7 files changed, 472 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 debayer.fs.glsl create mode 100644 debayer.vs.glsl create mode 100644 glsldebayer.c create mode 100755 linpoly.py create mode 100644 pixels.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..effd64c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +*.sw? diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e4d2f8c --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +.PHONY: all clean allclean + +all: glsldebayer + +pixels.raw: pixels.png + magick $< GRAY:$@ + +blob.o: pixels.raw debayer.vs.glsl debayer.fs.glsl + $(LD) -z noexecstack -r -b binary -o $@ $? + objcopy -R '*ABS*' --rename-section .data=.lrodata,contents,alloc,load,readonly,data $@ $@ + +glsldebayer: LDFLAGS += -lm -lSDL2 -lEGL -lGLESv2 +glsldebayer: glsldebayer.o blob.o + +clean: + -rm *.o glsldebayer + +allclean: clean + -rm *.raw diff --git a/debayer.fs.glsl b/debayer.fs.glsl new file mode 100644 index 0000000..e6eace4 --- /dev/null +++ b/debayer.fs.glsl @@ -0,0 +1,41 @@ +uniform sampler2D u_sampler; +uniform highp mat4 u_coef_r; +uniform highp mat4 u_coef_g; +uniform highp mat4 u_coef_b; +varying highp vec2 v_position; + +/* Evaluate power serial polynomial of degree 16, with 0-th order term 0 */ +highp float polyeval(highp mat4 c, highp float x){ + highp float y = c[3][3]; + for(int i=4, j=3; 0 +#include +#include +#include +#include + +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; +} diff --git a/linpoly.py b/linpoly.py new file mode 100755 index 0000000..3419a07 --- /dev/null +++ b/linpoly.py @@ -0,0 +1,90 @@ +#!/usr/bin/env -S python3 +import numpy +from numpy.polynomial import Polynomial, Chebyshev +from matplotlib.pyplot import * +from scipy.signal import windows +from scipy import interpolate + +measured = numpy.asarray([ + [1.0000, 0.9241, 0.9978, 0.9802], + [0.9666, 0.9209, 0.9966, 0.9774], + [0.9330, 0.9174, 0.9952, 0.9746], + [0.9000, 0.9135, 0.9936, 0.9715], + [0.8666, 0.9098, 0.9916, 0.9680], + [0.8333, 0.9057, 0.9893, 0.9646], + [0.8000, 0.9011, 0.9867, 0.9609], + [0.7666, 0.8968, 0.9834, 0.9569], + [0.7333, 0.8920, 0.9801, 0.9528], + [0.7000, 0.8869, 0.9764, 0.9483], + [0.6666, 0.8813, 0.9724, 0.9433], + [0.6333, 0.8753, 0.9681, 0.9384], + [0.6000, 0.8691, 0.9632, 0.9325], + [0.5666, 0.8622, 0.9580, 0.9264], + [0.5333, 0.8551, 0.9522, 0.9199], + [0.5000, 0.8471, 0.9464, 0.9129], + [0.4666, 0.8370, 0.9393, 0.9051], + [0.4333, 0.8234, 0.9321, 0.8964], + [0.4000, 0.8053, 0.9239, 0.8871], + [0.3666, 0.7829, 0.9143, 0.8763], + [0.3333, 0.7557, 0.9038, 0.8641], + [0.3000, 0.7223, 0.8917, 0.8500], + [0.2666, 0.6789, 0.8777, 0.8295], + [0.2333, 0.6224, 0.8610, 0.7973], + [0.2000, 0.5434, 0.8399, 0.7504], + [0.1666, 0.4629, 0.7992, 0.6823], + [0.1333, 0.3816, 0.7318, 0.5687], + [0.1000, 0.3009, 0.6064, 0.4406], + [0.0666, 0.2207, 0.4530, 0.3140], + [0.0333, 0.1400, 0.2403, 0.1872], + [0.0000, 0.0625, 0.0649, 0.0629], +]) + +xs = measured[:,0] +xs_ = numpy.linspace(-0.10, 1.15, 100) + +tukey = windows.tukey(measured.shape[0]); + +int_r = interpolate.PchipInterpolator(numpy.flip(xs), numpy.flip(measured[:,1]), extrapolate=True) +int_g = interpolate.PchipInterpolator(numpy.flip(xs), numpy.flip(measured[:,2]), extrapolate=True) +int_b = interpolate.PchipInterpolator(numpy.flip(xs), numpy.flip(measured[:,3]), extrapolate=True) + +off_r = int_r(0) +off_g = int_g(0) +off_b = int_b(0) + +k = 1./max( int_r(1)-off_r, int_g(1)-off_g, int_b(1)-off_b ) + +fit_r = Polynomial.fit(xs_, k*(int_r(xs_)-off_r), deg=16, domain=[0,1], window=[0,1], w=windows.tukey(xs_.shape[0])) +fit_g = Polynomial.fit(xs_, k*(int_g(xs_)-off_g), deg=16, domain=[0,1], window=[0,1], w=windows.tukey(xs_.shape[0])) +fit_b = Polynomial.fit(xs_, k*(int_b(xs_)-off_b), deg=16, domain=[0,1], window=[0,1], w=windows.tukey(xs_.shape[0])) + +def c_arr_print(label, arr): + print( label+"[] = {" + ', '.join([f'\n\t{v:e}' if 0 == i%4 else f'{v:e}' for i,v in enumerate(arr)]) + "};\n" ) + +c_arr_print('GLfloat const coef_r', fit_r.convert().coef[1:]) +c_arr_print('GLfloat const coef_g', fit_g.convert().coef[1:]) +c_arr_print('GLfloat const coef_b', fit_b.convert().coef[1:]) + +def polyeval(c, xs): + C_ = numpy.flip(c) + ys = numpy.ones(xs.size)*C_[0] + for c in C_[1:]: + ys = ys*xs + c + return ys*xs + +if True: + plot(xs, measured[:,1], '+') + plot(xs_, fit_r(xs_)) + plot(xs_, polyeval(fit_r.convert().coef[1:], xs_), '*') + + plot(xs, measured[:,2], '+') + plot(xs_, fit_g(xs_)) + plot(xs_, polyeval(fit_g.convert().coef[1:], xs_), '*') + + plot(xs, measured[:,3], '+') + plot(xs_, fit_b(xs_)) + plot(xs_, polyeval(fit_b.convert().coef[1:], xs_), '*') + + xlim(0,1) + ylim(0,1) + show() diff --git a/pixels.png b/pixels.png new file mode 100644 index 0000000..d96c1dd Binary files /dev/null and b/pixels.png differ -- cgit v1.3.1