From f1947f134e35e6ec76641f4d9294a2b7236ef5b0 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Thu, 8 Apr 2021 00:16:17 +1000 Subject: [PATCH 01/21] Rework pipeline to allow for DMA support The process pipeline is now tasked with releasing the camera buffer, rather than being given a copy. This enables being able to avoid the copy in the future if it has a performance advantage. --- camera.c | 53 ++++++++++++++++++++++++++++------------- camera.h | 12 +++++----- io_pipeline.c | 33 +++++++++++++++----------- io_pipeline.h | 2 ++ pipeline.c | 30 ++++++++++++++++++++--- pipeline.h | 4 +++- process_pipeline.c | 58 +++++++++++++++++++++++++++------------------ process_pipeline.h | 3 ++- tools/camera_test.c | 31 ++++++++++++------------ 9 files changed, 146 insertions(+), 80 deletions(-) diff --git a/camera.c b/camera.c index 6e80781..f6fbf37 100644 --- a/camera.c +++ b/camera.c @@ -6,6 +6,7 @@ #include #include #include +#include #define MAX_VIDEO_BUFFERS 20 @@ -205,6 +206,7 @@ xioctl(int fd, int request, void *arg) struct video_buffer { uint32_t length; uint8_t *data; + int fd; }; struct _MPCamera { @@ -465,6 +467,17 @@ mp_camera_start_capture(MPCamera *camera) break; } + struct v4l2_exportbuffer expbuf = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .index = i, + }; + if (xioctl(camera->video_fd, VIDIOC_EXPBUF, &expbuf) == -1) { + errno_printerr("VIDIOC_EXPBUF"); + break; + } + + camera->buffers[i].fd = expbuf.fd; + ++camera->num_buffers; } @@ -510,6 +523,10 @@ error: -1) { errno_printerr("munmap"); } + + if (close(camera->buffers[i].fd) == -1) { + errno_printerr("close"); + } } // Reset allocated buffers @@ -543,6 +560,10 @@ mp_camera_stop_capture(MPCamera *camera) -1) { errno_printerr("munmap"); } + + if (close(camera->buffers[i].fd) == -1) { + errno_printerr("close"); + } } camera->num_buffers = 0; @@ -565,8 +586,7 @@ mp_camera_is_capturing(MPCamera *camera) } bool -mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *), - void *user_data) +mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer) { struct v4l2_buffer buf = {}; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -606,24 +626,23 @@ mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *), mp_pixel_format_width_to_bytes(pixel_format, width) * height); assert(bytesused == camera->buffers[buf.index].length); - MPImage image = { - .pixel_format = pixel_format, - .width = width, - .height = height, - .data = camera->buffers[buf.index].data, - }; + buffer->index = buf.index; + buffer->data = camera->buffers[buf.index].data; + buffer->fd = camera->buffers[buf.index].fd; - callback(image, user_data); + return true; +} - // The callback may have stopped the capture, only queue the buffer if we're - // still capturing. - if (mp_camera_is_capturing(camera)) { - if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) { - errno_printerr("VIDIOC_QBUF"); - return false; - } +bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index) +{ + struct v4l2_buffer buf = {}; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = buffer_index; + if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) { + errno_printerr("VIDIOC_QBUF"); + return false; } - return true; } diff --git a/camera.h b/camera.h index 3004e8e..323ac63 100644 --- a/camera.h +++ b/camera.h @@ -45,11 +45,11 @@ typedef struct { bool mp_camera_mode_is_equivalent(const MPCameraMode *m1, const MPCameraMode *m2); typedef struct { - uint32_t pixel_format; - uint32_t width; - uint32_t height; + uint32_t index; + uint8_t *data; -} MPImage; + int fd; +} MPBuffer; typedef struct _MPCamera MPCamera; @@ -67,8 +67,8 @@ bool mp_camera_set_mode(MPCamera *camera, MPCameraMode *mode); bool mp_camera_start_capture(MPCamera *camera); bool mp_camera_stop_capture(MPCamera *camera); bool mp_camera_is_capturing(MPCamera *camera); -bool mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *), - void *user_data); +bool mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer); +bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index); typedef struct _MPCameraModeList MPCameraModeList; diff --git a/io_pipeline.c b/io_pipeline.c index 980a722..1ebb93d 100644 --- a/io_pipeline.c +++ b/io_pipeline.c @@ -324,6 +324,19 @@ mp_io_pipeline_capture() mp_pipeline_invoke(pipeline, capture, NULL, 0); } +static void +release_buffer(MPPipeline *pipeline, const uint32_t *buffer_index) +{ + struct camera_info *info = &cameras[camera->index]; + + mp_camera_release_buffer(info->camera, *buffer_index); +} + +void mp_io_pipeline_release_buffer(uint32_t buffer_index) +{ + mp_pipeline_invoke(pipeline, (MPPipelineCallback) release_buffer, &buffer_index, sizeof(uint32_t)); +} + static void update_controls() { @@ -375,7 +388,7 @@ update_controls() } static void -on_frame(MPImage image, void *data) +on_frame(MPBuffer buffer, void * _data) { // Only update controls right after a frame was captured update_controls(); @@ -384,13 +397,13 @@ on_frame(MPImage image, void *data) // presumably from buffers made ready during the switch. Ignore these. if (just_switched_mode) { if (blank_frame_count < 20) { - // Only check a 50x50 area + // Only check a 10x10 area size_t test_size = - MIN(50, image.width) * MIN(50, image.height); + MIN(10, mode.width) * MIN(10, mode.height); bool image_is_blank = true; for (size_t i = 0; i < test_size; ++i) { - if (image.data[i] != 0) { + if (buffer.data[i] != 0) { image_is_blank = false; } } @@ -407,17 +420,8 @@ on_frame(MPImage image, void *data) blank_frame_count = 0; } - // Copy from the camera buffer - size_t size = - mp_pixel_format_width_to_bytes(image.pixel_format, image.width) * - image.height; - uint8_t *buffer = malloc(size); - memcpy(buffer, image.data, size); - - image.data = buffer; - // Send the image off for processing - mp_process_pipeline_process_image(image); + mp_process_pipeline_process_image(buffer); if (captures_remaining > 0) { --captures_remaining; @@ -465,6 +469,7 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state) struct camera_info *info = &cameras[camera->index]; struct device_info *dev_info = &devices[info->device_index]; + mp_process_pipeline_sync(); mp_camera_stop_capture(info->camera); mp_device_setup_link(dev_info->device, info->pad_id, dev_info->interface_pad_id, false); diff --git a/io_pipeline.h b/io_pipeline.h index 10a6298..1814f84 100644 --- a/io_pipeline.h +++ b/io_pipeline.h @@ -23,4 +23,6 @@ void mp_io_pipeline_stop(); void mp_io_pipeline_focus(); void mp_io_pipeline_capture(); +void mp_io_pipeline_release_buffer(uint32_t buffer_index); + void mp_io_pipeline_update_state(const struct mp_io_pipeline_state *state); diff --git a/pipeline.c b/pipeline.c index fcfa4c2..80715f0 100644 --- a/pipeline.c +++ b/pipeline.c @@ -65,6 +65,27 @@ mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback, } } +static bool +unlock_mutex(GMutex *mutex) +{ + g_mutex_unlock(mutex); + return false; +} + +void +mp_pipeline_sync(MPPipeline *pipeline) +{ + GMutex mutex; + g_mutex_init(&mutex); + g_mutex_lock(&mutex); + + g_main_context_invoke_full(pipeline->main_context, G_PRIORITY_LOW, (GSourceFunc)unlock_mutex, &mutex, NULL); + g_mutex_lock(&mutex); + g_mutex_unlock(&mutex); + + g_mutex_clear(&mutex); +} + void mp_pipeline_free(MPPipeline *pipeline) { @@ -80,21 +101,24 @@ mp_pipeline_free(MPPipeline *pipeline) struct capture_source_args { MPCamera *camera; - void (*callback)(MPImage, void *); + void (*callback)(MPBuffer, void *); void *user_data; }; static bool on_capture(int fd, GIOCondition condition, struct capture_source_args *args) { - mp_camera_capture_image(args->camera, args->callback, args->user_data); + MPBuffer buffer; + if (mp_camera_capture_buffer(args->camera, &buffer)) { + args->callback(buffer, args->user_data); + } return true; } // Not thread safe GSource * mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera, - void (*callback)(MPImage, void *), void *user_data) + void (*callback)(MPBuffer, void *), void *user_data) { int video_fd = mp_camera_get_video_fd(camera); GSource *video_source = g_unix_fd_source_new(video_fd, G_IO_IN); diff --git a/pipeline.h b/pipeline.h index 6f71faa..67eaeff 100644 --- a/pipeline.h +++ b/pipeline.h @@ -11,8 +11,10 @@ typedef void (*MPPipelineCallback)(MPPipeline *, const void *); MPPipeline *mp_pipeline_new(); void mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback, const void *data, size_t size); +// Wait until all pending tasks have completed +void mp_pipeline_sync(MPPipeline *pipeline); void mp_pipeline_free(MPPipeline *pipeline); GSource *mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera, - void (*callback)(MPImage, void *), + void (*callback)(MPBuffer, void *), void *user_data); diff --git a/process_pipeline.c b/process_pipeline.c index 64003a2..7a43c59 100644 --- a/process_pipeline.c +++ b/process_pipeline.c @@ -2,6 +2,7 @@ #include "pipeline.h" #include "zbar_pipeline.h" +#include "io_pipeline.h" #include "main.h" #include "config.h" #include "quickpreview.h" @@ -134,21 +135,27 @@ mp_process_pipeline_stop() mp_zbar_pipeline_stop(); } +void +mp_process_pipeline_sync() +{ + mp_pipeline_sync(pipeline); +} + static cairo_surface_t * -process_image_for_preview(const MPImage *image) +process_image_for_preview(const uint8_t *image) { uint32_t surface_width, surface_height, skip; quick_preview_size(&surface_width, &surface_height, &skip, preview_width, - preview_height, image->width, image->height, - image->pixel_format, camera->rotate); + preview_height, mode.width, mode.height, + mode.pixel_format, camera->rotate); cairo_surface_t *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->data, - image->width, image->height, image->pixel_format, + 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); @@ -174,7 +181,7 @@ process_image_for_preview(const MPImage *image) } static void -process_image_for_capture(const MPImage *image, int count) +process_image_for_capture(const uint8_t *image, int count) { time_t rawtime; time(&rawtime); @@ -193,8 +200,8 @@ process_image_for_capture(const MPImage *image, int count) // Define TIFF thumbnail TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 1); - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width >> 4); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height >> 4); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width >> 4); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height >> 4); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); @@ -241,8 +248,8 @@ process_image_for_capture(const MPImage *image, int count) // Write black thumbnail, only windows uses this { unsigned char *buf = - (unsigned char *)calloc(1, (int)image->width >> 4); - for (int row = 0; row < (image->height >> 4); row++) { + (unsigned char *)calloc(1, (int)mode.width >> 4); + for (int row = 0; row < (mode.height >> 4); row++) { TIFFWriteScanline(tif, buf, row, 0); } free(buf); @@ -251,8 +258,8 @@ process_image_for_capture(const MPImage *image, int count) // Define main photo TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0); - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA); TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); @@ -274,9 +281,9 @@ process_image_for_capture(const MPImage *image, int count) TIFFCheckpointDirectory(tif); printf("Writing frame to %s\n", fname); - unsigned char *pLine = (unsigned char *)malloc(image->width); - for (int row = 0; row < image->height; row++) { - TIFFWriteScanline(tif, image->data + (row * image->width), row, 0); + unsigned char *pLine = (unsigned char *)malloc(mode.width); + for (int row = 0; row < mode.height; row++) { + TIFFWriteScanline(tif, (void *) image + (row * mode.width), row, 0); } free(pLine); TIFFWriteDirectory(tif); @@ -293,7 +300,7 @@ process_image_for_capture(const MPImage *image, int count) TIFFSetField(tif, EXIFTAG_EXPOSURETIME, (mode.frame_interval.numerator / (float)mode.frame_interval.denominator) / - ((float)image->height / (float)exposure)); + ((float)mode.height / (float)exposure)); uint16_t isospeed[1]; isospeed[0] = (uint16_t)remap(gain - 1, 0, gain_max, camera->iso_min, camera->iso_max); @@ -401,9 +408,14 @@ process_capture_burst(cairo_surface_t *thumb) } static void -process_image(MPPipeline *pipeline, const MPImage *image) +process_image(MPPipeline *pipeline, const MPBuffer *buffer) { - assert(image->width == mode.width && image->height == mode.height); + size_t size = + mp_pixel_format_width_to_bytes(mode.pixel_format, mode.width) * + mode.height; + uint8_t *image = malloc(size); + memcpy(image, buffer->data, size); + mp_io_pipeline_release_buffer(buffer->index); cairo_surface_t *thumb = process_image_for_preview(image); @@ -423,7 +435,7 @@ process_image(MPPipeline *pipeline, const MPImage *image) assert(!thumb); } - free(image->data); + free(image); ++frames_processed; if (captures_remaining == 0) { @@ -432,19 +444,19 @@ process_image(MPPipeline *pipeline, const MPImage *image) } void -mp_process_pipeline_process_image(MPImage image) +mp_process_pipeline_process_image(MPBuffer buffer) { // If we haven't processed the previous frame yet, drop this one if (frames_received != frames_processed && !is_capturing) { printf("Dropped frame at capture\n"); - free(image.data); + mp_io_pipeline_release_buffer(buffer.index); return; } ++frames_received; - mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image, - sizeof(MPImage)); + mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &buffer, + sizeof(MPBuffer)); } static void diff --git a/process_pipeline.h b/process_pipeline.h index 40cadd7..351a576 100644 --- a/process_pipeline.h +++ b/process_pipeline.h @@ -24,7 +24,8 @@ struct mp_process_pipeline_state { void mp_process_pipeline_start(); void mp_process_pipeline_stop(); +void mp_process_pipeline_sync(); -void mp_process_pipeline_process_image(MPImage image); +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); diff --git a/tools/camera_test.c b/tools/camera_test.c index df88dbf..df7d923 100644 --- a/tools/camera_test.c +++ b/tools/camera_test.c @@ -15,20 +15,6 @@ get_time() return t.tv_sec + t.tv_usec * 1e-6; } -void -on_capture(MPImage image, void *user_data) -{ - size_t num_bytes = - mp_pixel_format_width_to_bytes(image.pixel_format, image.width) * - image.height; - uint8_t *data = malloc(num_bytes); - memcpy(data, image.data, num_bytes); - - printf(" first byte: %d.", data[0]); - - free(data); -} - int main(int argc, char *argv[]) { @@ -184,7 +170,22 @@ main(int argc, char *argv[]) (last - start_capture) * 1000); for (int i = 0; i < 10; ++i) { - mp_camera_capture_image(camera, on_capture, NULL); + MPBuffer buffer; + if (!mp_camera_capture_buffer(camera, &buffer)) { + printf(" Failed to capture buffer\n"); + } + + size_t num_bytes = + mp_pixel_format_width_to_bytes(m->pixel_format, m->width) * + m->height; + uint8_t *data = malloc(num_bytes); + memcpy(data, buffer.data, num_bytes); + + printf(" first byte: %d.", data[0]); + + free(data); + + mp_camera_release_buffer(camera, buffer.index); double now = get_time(); printf(" capture took %fms\n", (now - last) * 1000); From 1ffaba5f7ee6478bce6cf1297a02e0faba130741 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Fri, 9 Apr 2021 22:57:14 +1000 Subject: [PATCH 02/21] First pass at OpenGL debayering --- data/debayer.frag | 28 ++ data/debayer.vert | 19 ++ gl_quickpreview.c | 216 ++++++++++++++ gl_quickpreview.h | 21 ++ gl_utils.h | 34 +++ main.c | 8 + meson.build | 8 +- process_pipeline.c | 236 +++++++++++++++- process_pipeline.h | 4 + renderdoc/app.h | 688 +++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1248 insertions(+), 14 deletions(-) create mode 100644 data/debayer.frag create mode 100644 data/debayer.vert create mode 100644 gl_quickpreview.c create mode 100644 gl_quickpreview.h create mode 100644 gl_utils.h create mode 100644 renderdoc/app.h diff --git a/data/debayer.frag b/data/debayer.frag new file mode 100644 index 0000000..75addcb --- /dev/null +++ b/data/debayer.frag @@ -0,0 +1,28 @@ +precision mediump float; + +uniform sampler2D texture; + +varying vec2 uv1; +varying vec2 uv2; +varying vec2 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))); + + vec2 arrangement = mod(pixel_coord, 2.0); + + vec2 is_top_left = step(1.0, arrangement); + + 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); + + gl_FragColor = vec4(color, 0); +} diff --git a/data/debayer.vert b/data/debayer.vert new file mode 100644 index 0000000..f205536 --- /dev/null +++ b/data/debayer.vert @@ -0,0 +1,19 @@ +precision mediump float; + +attribute vec2 vert; +attribute vec2 tex_coord; + +uniform mat3 transform; +uniform vec2 pixel_size; + +varying vec2 uv1; +varying vec2 uv2; +varying vec2 pixel_coord; + +void main() { + uv1 = tex_coord - pixel_size / 2.0; + uv2 = tex_coord + pixel_size / 2.0; + pixel_coord = uv1 / pixel_size; + + gl_Position = vec4(transform * vec3(vert, 1), 1); +} diff --git a/gl_quickpreview.c b/gl_quickpreview.c new file mode 100644 index 0000000..2bc1a8f --- /dev/null +++ b/gl_quickpreview.c @@ -0,0 +1,216 @@ +#include "camera.h" +#include "gl_quickpreview.h" +#include "gl_utils.h" +#include +#include + +#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_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; +} + +GLQuickPreview *gl_quick_preview_new() +{ + GLuint frame_buffer; + 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); + + glBindAttribLocation(program, VERTEX_ATTRIBUTE, "vert"); + check_gl(); + glBindAttribLocation(program, TEX_COORD_ATTRIBUTE, "tex_coord"); + check_gl(); + 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(); + + GLQuickPreview *self = malloc(sizeof(GLQuickPreview)); + self->frame_buffer = frame_buffer; + self->program = program; + + self->uniform_transform = glGetUniformLocation(self->program, "transform"); + check_gl(); + + self->uniform_pixel_size = glGetUniformLocation(self->program, "pixel_size"); + check_gl(); + + self->uniform_texture = glGetUniformLocation(self->program, "texture"); + check_gl(); + + return self; +} + +void gl_quick_preview_free(GLQuickPreview *self) +{ + glDeleteFramebuffers(1, &self->frame_buffer); + + free(self); +} + +bool +ql_quick_preview_supports_format(GLQuickPreview *self, const MPPixelFormat format) +{ + return format == MP_PIXEL_FMT_BGGR8; +} + +bool +gl_quick_preview(GLQuickPreview *self, + GLuint dst_id, + const uint32_t dst_width, const uint32_t dst_height, + GLuint source_id, + const uint32_t src_width, const uint32_t src_height, + const MPPixelFormat format, + const uint32_t rotation, + const bool mirrored, + const float *colormatrix, + const uint8_t blacklevel) +{ + assert(ql_quick_preview_supports_format(self, format)); + + glBindFramebuffer(GL_FRAMEBUFFER, self->frame_buffer); + glBindTexture(GL_TEXTURE_2D, dst_id); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_id, 0); + check_gl(); + + glViewport(0, 0, dst_width, dst_height); + check_gl(); + + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + glUseProgram(self->program); + check_gl(); + + GLfloat rotation_list[4] = { 1, 0, -1, 0 }; + int rotation_index = 4 - rotation / 90; + + GLfloat sin_rot = rotation_list[rotation_index]; + GLfloat cos_rot = rotation_list[(rotation_index + 1) % 4]; + GLfloat scale_x = mirrored ? 1 : -1; + GLfloat matrix[9] = { + cos_rot * scale_x, sin_rot, 0, + -sin_rot * scale_x, cos_rot, 0, + 0, 0, 1, + }; + glUniformMatrix3fv(self->uniform_transform, 1, GL_FALSE, matrix); + 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); + check_gl(); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, source_id); + glUniform1i(self->uniform_texture, 0); + check_gl(); + + glVertexAttribPointer(VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, square_vertices); + check_gl(); + glEnableVertexAttribArray(VERTEX_ATTRIBUTE); + check_gl(); + glVertexAttribPointer(TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, square_texcoords); + check_gl(); + glEnableVertexAttribArray(TEX_COORD_ATTRIBUTE); + check_gl(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + check_gl(); + + // TODO: Render + // glClearColor(0, 0, 1, 0); + // glClear(GL_COLOR_BUFFER_BIT); + + // glBindTexture(GL_TEXTURE_2D, 0); + // glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glUseProgram(0); + check_gl(); + + return true; +} diff --git a/gl_quickpreview.h b/gl_quickpreview.h new file mode 100644 index 0000000..fede8c5 --- /dev/null +++ b/gl_quickpreview.h @@ -0,0 +1,21 @@ +#include "quickpreview.h" +#include +#include +#include + +typedef struct _GLQuickPreview GLQuickPreview; + +GLQuickPreview* gl_quick_preview_new(); +void gl_quick_preview_free(GLQuickPreview *self); + +bool ql_quick_preview_supports_format(GLQuickPreview *self, const MPPixelFormat format); +bool gl_quick_preview(GLQuickPreview *self, + GLuint dst_id, + const uint32_t dst_width, const uint32_t dst_height, + GLuint source_id, + const uint32_t src_width, const uint32_t src_height, + const MPPixelFormat format, + const uint32_t rotation, + const bool mirrored, + const float *colormatrix, + const uint8_t blacklevel); diff --git a/gl_utils.h b/gl_utils.h new file mode 100644 index 0000000..bb4c268 --- /dev/null +++ b/gl_utils.h @@ -0,0 +1,34 @@ +#include + +#define check_gl() __check_gl(__FILE__, __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; + } + + printf("GL error at %s:%d - %s\n", file, line, name); +} diff --git a/main.c b/main.c index 63471bc..dd825fe 100644 --- a/main.c +++ b/main.c @@ -21,6 +21,7 @@ #include "camera_config.h" #include "quickpreview.h" #include "io_pipeline.h" +#include "process_pipeline.h" enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER }; @@ -686,6 +687,12 @@ 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)); +} + int main(int argc, char *argv[]) { @@ -726,6 +733,7 @@ main(int argc, char *argv[]) control_slider = 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(shutter, "clicked", G_CALLBACK(on_shutter_clicked), NULL); g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked), diff --git a/meson.build b/meson.build index 56df6fe..40f31c6 100644 --- a/meson.build +++ b/meson.build @@ -4,6 +4,8 @@ gtkdep = dependency('gtk+-3.0') tiff = dependency('libtiff-4') zbar = dependency('zbar') threads = dependency('threads') +gl = dependency('gl') +egl = dependency('egl') cc = meson.get_compiler('c') libm = cc.find_library('m', required: false) @@ -31,6 +33,7 @@ executable('megapixels', 'main.c', 'ini.c', 'quickpreview.c', + 'gl_quickpreview.c', 'camera.c', 'device.c', 'pipeline.c', @@ -40,8 +43,9 @@ executable('megapixels', 'zbar_pipeline.c', 'matrix.c', resources, - dependencies : [gtkdep, libm, tiff, zbar, threads], - install : true) + dependencies : [gtkdep, libm, tiff, zbar, threads, gl, egl], + install : true, + link_args : '-Wl,-ldl') install_data(['data/org.postmarketos.Megapixels.desktop'], install_dir : get_option('datadir') / 'applications') diff --git a/process_pipeline.c b/process_pipeline.c index 7a43c59..af14120 100644 --- a/process_pipeline.c +++ b/process_pipeline.c @@ -6,12 +6,18 @@ #include "main.h" #include "config.h" #include "quickpreview.h" +#include "gl_quickpreview.h" #include #include #include #include #include +#include "gl_utils.h" +#include +#include +#include + #define TIFFTAG_FORWARDMATRIX1 50964 static const float colormatrix_srgb[] = { 3.2409, -1.5373, -0.4986, -0.9692, 1.8759, @@ -141,24 +147,230 @@ mp_process_pipeline_sync() mp_pipeline_sync(pipeline); } +#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 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]; + +static PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA; + +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"; +} + +#define RENDERDOC + +#ifdef RENDERDOC +#include +#include +RENDERDOC_API_1_1_2 *rdoc_api = NULL; +#endif + +static void +init_gl(MPPipeline *pipeline, GdkWindow **window) +{ +#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 + + 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); + + EGLint major, minor; + if (!eglInitialize(egl_display, &major, &minor)) { + printf("Failed to initialize egl: %s\n", egl_get_error_str()); + return; + } + + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + printf("Failed to bind OpenGL ES: %s\n", egl_get_error_str()); + return; + } + + printf("extensions: %s\n", eglQueryString(egl_display, EGL_EXTENSIONS)); + + EGLint config_attrs[1] = { + EGL_NONE + }; + + 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; + } + + if (num_configs != 1) { + printf("No egl configs found: %s\n", egl_get_error_str()); + 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; + } + + 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()); + return; + } + + check_gl(); + + eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC) + eglGetProcAddress("eglExportDMABUFImageMESA"); + + // Generate textures for the buffers + // GLuint textures[NUM_BUFFERS * 2]; + // glGenTextures(NUM_BUFFERS * 2, textures); + + // for (size_t i = 0; i < NUM_BUFFERS; ++i) { + // input_buffers[i].texture_id = textures[i]; + // input_buffers[i].egl_image = EGL_NO_IMAGE; + // input_buffers[i].dma_fd = -1; + // } + // for (size_t i = 0; i < NUM_BUFFERS; ++i) { + // output_buffers[i].texture_id = textures[NUM_BUFFERS + i]; + // output_buffers[i].egl_image = EGL_NO_IMAGE; + // output_buffers[i].dma_fd = -1; + // } + + check_gl(); + gl_quick_preview_state = gl_quick_preview_new(); + check_gl(); + + printf("Initialized OpenGL\n"); +} + +void +mp_process_pipeline_init_gl(GdkWindow *window) +{ + mp_pipeline_invoke(pipeline, (MPPipelineCallback) init_gl, &window, sizeof(GdkWindow *)); +} + static cairo_surface_t * process_image_for_preview(const uint8_t *image) { - 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); + cairo_surface_t *surface; - cairo_surface_t *surface = cairo_image_surface_create( - CAIRO_FORMAT_RGB24, surface_width, surface_height); + if (gl_quick_preview_state && ql_quick_preview_supports_format(gl_quick_preview_state, mode.pixel_format)) { +#ifdef RENDERDOC + if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); +#endif + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + check_gl(); - uint8_t *pixels = cairo_image_surface_get_data(surface); + GLuint textures[2]; + glGenTextures(2, textures); - 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); + 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); + 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); + uint8_t *pixels = cairo_image_surface_get_data(surface); + glReadPixels(0, 0, preview_width, preview_height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + check_gl(); + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + +#ifdef RENDERDOC + 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); + } // Create a thumbnail from the preview for the last capture cairo_surface_t *thumb = NULL; diff --git a/process_pipeline.h b/process_pipeline.h index 351a576..28f1b78 100644 --- a/process_pipeline.h +++ b/process_pipeline.h @@ -2,6 +2,8 @@ #include "camera_config.h" +typedef struct _GdkWindow GdkWindow; + struct mp_process_pipeline_state { const struct mp_camera_config *camera; MPCameraMode mode; @@ -26,6 +28,8 @@ 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_process_image(MPBuffer buffer); void mp_process_pipeline_capture(); void mp_process_pipeline_update_state(const struct mp_process_pipeline_state *state); diff --git a/renderdoc/app.h b/renderdoc/app.h new file mode 100644 index 0000000..4e1fec2 --- /dev/null +++ b/renderdoc/app.h @@ -0,0 +1,688 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html +// + +#if !defined(RENDERDOC_NO_STDINT) +#include +#endif + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define RENDERDOC_CC __cdecl +#elif defined(__linux__) +#define RENDERDOC_CC +#elif defined(__APPLE__) +#define RENDERDOC_CC +#else +#error "Unknown platform" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Constants not used directly in below API + +// This is a GUID/magic value used for when applications pass a path where shader debug +// information can be found to match up with a stripped shader. +// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue = +// RENDERDOC_ShaderDebugMagicValue_value +#define RENDERDOC_ShaderDebugMagicValue_struct \ + { \ + 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// as an alternative when you want a byte array (assuming x86 endianness): +#define RENDERDOC_ShaderDebugMagicValue_bytearray \ + { \ + 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// truncated version when only a uint64_t is available (e.g. Vulkan tags): +#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc capture options +// + +typedef enum RENDERDOC_CaptureOption { + // Allow the application to enable vsync + // + // Default - enabled + // + // 1 - The application can enable or disable vsync at will + // 0 - vsync is force disabled + eRENDERDOC_Option_AllowVSync = 0, + + // Allow the application to enable fullscreen + // + // Default - enabled + // + // 1 - The application can enable or disable fullscreen at will + // 0 - fullscreen is force disabled + eRENDERDOC_Option_AllowFullscreen = 1, + + // Record API debugging events and messages + // + // Default - disabled + // + // 1 - Enable built-in API debugging features and records the results into + // the capture, which is matched up with events on replay + // 0 - no API debugging is forcibly enabled + eRENDERDOC_Option_APIValidation = 2, + eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum + + // Capture CPU callstacks for API events + // + // Default - disabled + // + // 1 - Enables capturing of callstacks + // 0 - no callstacks are captured + eRENDERDOC_Option_CaptureCallstacks = 3, + + // When capturing CPU callstacks, only capture them from drawcalls. + // This option does nothing without the above option being enabled + // + // Default - disabled + // + // 1 - Only captures callstacks for drawcall type API events. + // Ignored if CaptureCallstacks is disabled + // 0 - Callstacks, if enabled, are captured for every event. + eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4, + + // Specify a delay in seconds to wait for a debugger to attach, after + // creating or injecting into a process, before continuing to allow it to run. + // + // 0 indicates no delay, and the process will run immediately after injection + // + // Default - 0 seconds + // + eRENDERDOC_Option_DelayForDebugger = 5, + + // Verify buffer access. This includes checking the memory returned by a Map() call to + // detect any out-of-bounds modification, as well as initialising buffers with undefined contents + // to a marker value to catch use of uninitialised memory. + // + // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do + // not do the same kind of interception & checking and undefined contents are really undefined. + // + // Default - disabled + // + // 1 - Verify buffer access + // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in + // RenderDoc. + eRENDERDOC_Option_VerifyBufferAccess = 6, + + // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites. + // This option now controls the filling of uninitialised buffers with 0xdddddddd which was + // previously always enabled + eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess, + + // Hooks any system API calls that create child processes, and injects + // RenderDoc into them recursively with the same options. + // + // Default - disabled + // + // 1 - Hooks into spawned child processes + // 0 - Child processes are not hooked by RenderDoc + eRENDERDOC_Option_HookIntoChildren = 7, + + // By default RenderDoc only includes resources in the final capture necessary + // for that frame, this allows you to override that behaviour. + // + // Default - disabled + // + // 1 - all live resources at the time of capture are included in the capture + // and available for inspection + // 0 - only the resources referenced by the captured frame are included + eRENDERDOC_Option_RefAllResources = 8, + + // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or + // getting it will be ignored, to allow compatibility with older versions. + // In v1.1 the option acts as if it's always enabled. + // + // By default RenderDoc skips saving initial states for resources where the + // previous contents don't appear to be used, assuming that writes before + // reads indicate previous contents aren't used. + // + // Default - disabled + // + // 1 - initial contents at the start of each captured frame are saved, even if + // they are later overwritten or cleared before being used. + // 0 - unless a read is detected, initial contents will not be saved and will + // appear as black or empty data. + eRENDERDOC_Option_SaveAllInitials = 9, + + // In APIs that allow for the recording of command lists to be replayed later, + // RenderDoc may choose to not capture command lists before a frame capture is + // triggered, to reduce overheads. This means any command lists recorded once + // and replayed many times will not be available and may cause a failure to + // capture. + // + // NOTE: This is only true for APIs where multithreading is difficult or + // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option + // and always capture all command lists since the API is heavily oriented + // around it and the overheads have been reduced by API design. + // + // 1 - All command lists are captured from the start of the application + // 0 - Command lists are only captured if their recording begins during + // the period when a frame capture is in progress. + eRENDERDOC_Option_CaptureAllCmdLists = 10, + + // Mute API debugging output when the API validation mode option is enabled + // + // Default - enabled + // + // 1 - Mute any API debug messages from being displayed or passed through + // 0 - API debugging is displayed as normal + eRENDERDOC_Option_DebugOutputMute = 11, + + // Option to allow vendor extensions to be used even when they may be + // incompatible with RenderDoc and cause corrupted replays or crashes. + // + // Default - inactive + // + // No values are documented, this option should only be used when absolutely + // necessary as directed by a RenderDoc developer. + eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, + +} RENDERDOC_CaptureOption; + +// Sets an option that controls how RenderDoc behaves on capture. +// +// Returns 1 if the option and value are valid +// Returns 0 if either is invalid and the option is unchanged +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val); +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val); + +// Gets the current value of an option as a uint32_t +// +// If the option is invalid, 0xffffffff is returned +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt); + +// Gets the current value of an option as a float +// +// If the option is invalid, -FLT_MAX is returned +typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt); + +typedef enum RENDERDOC_InputButton { + // '0' - '9' matches ASCII values + eRENDERDOC_Key_0 = 0x30, + eRENDERDOC_Key_1 = 0x31, + eRENDERDOC_Key_2 = 0x32, + eRENDERDOC_Key_3 = 0x33, + eRENDERDOC_Key_4 = 0x34, + eRENDERDOC_Key_5 = 0x35, + eRENDERDOC_Key_6 = 0x36, + eRENDERDOC_Key_7 = 0x37, + eRENDERDOC_Key_8 = 0x38, + eRENDERDOC_Key_9 = 0x39, + + // 'A' - 'Z' matches ASCII values + eRENDERDOC_Key_A = 0x41, + eRENDERDOC_Key_B = 0x42, + eRENDERDOC_Key_C = 0x43, + eRENDERDOC_Key_D = 0x44, + eRENDERDOC_Key_E = 0x45, + eRENDERDOC_Key_F = 0x46, + eRENDERDOC_Key_G = 0x47, + eRENDERDOC_Key_H = 0x48, + eRENDERDOC_Key_I = 0x49, + eRENDERDOC_Key_J = 0x4A, + eRENDERDOC_Key_K = 0x4B, + eRENDERDOC_Key_L = 0x4C, + eRENDERDOC_Key_M = 0x4D, + eRENDERDOC_Key_N = 0x4E, + eRENDERDOC_Key_O = 0x4F, + eRENDERDOC_Key_P = 0x50, + eRENDERDOC_Key_Q = 0x51, + eRENDERDOC_Key_R = 0x52, + eRENDERDOC_Key_S = 0x53, + eRENDERDOC_Key_T = 0x54, + eRENDERDOC_Key_U = 0x55, + eRENDERDOC_Key_V = 0x56, + eRENDERDOC_Key_W = 0x57, + eRENDERDOC_Key_X = 0x58, + eRENDERDOC_Key_Y = 0x59, + eRENDERDOC_Key_Z = 0x5A, + + // leave the rest of the ASCII range free + // in case we want to use it later + eRENDERDOC_Key_NonPrintable = 0x100, + + eRENDERDOC_Key_Divide, + eRENDERDOC_Key_Multiply, + eRENDERDOC_Key_Subtract, + eRENDERDOC_Key_Plus, + + eRENDERDOC_Key_F1, + eRENDERDOC_Key_F2, + eRENDERDOC_Key_F3, + eRENDERDOC_Key_F4, + eRENDERDOC_Key_F5, + eRENDERDOC_Key_F6, + eRENDERDOC_Key_F7, + eRENDERDOC_Key_F8, + eRENDERDOC_Key_F9, + eRENDERDOC_Key_F10, + eRENDERDOC_Key_F11, + eRENDERDOC_Key_F12, + + eRENDERDOC_Key_Home, + eRENDERDOC_Key_End, + eRENDERDOC_Key_Insert, + eRENDERDOC_Key_Delete, + eRENDERDOC_Key_PageUp, + eRENDERDOC_Key_PageDn, + + eRENDERDOC_Key_Backspace, + eRENDERDOC_Key_Tab, + eRENDERDOC_Key_PrtScrn, + eRENDERDOC_Key_Pause, + + eRENDERDOC_Key_Max, +} RENDERDOC_InputButton; + +// Sets which key or keys can be used to toggle focus between multiple windows +// +// If keys is NULL or num is 0, toggle keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num); + +// Sets which key or keys can be used to capture the next frame +// +// If keys is NULL or num is 0, captures keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num); + +typedef enum RENDERDOC_OverlayBits { + // This single bit controls whether the overlay is enabled or disabled globally + eRENDERDOC_Overlay_Enabled = 0x1, + + // Show the average framerate over several seconds as well as min/max + eRENDERDOC_Overlay_FrameRate = 0x2, + + // Show the current frame number + eRENDERDOC_Overlay_FrameNumber = 0x4, + + // Show a list of recent captures, and how many captures have been made + eRENDERDOC_Overlay_CaptureList = 0x8, + + // Default values for the overlay mask + eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate | + eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList), + + // Enable all bits + eRENDERDOC_Overlay_All = ~0U, + + // Disable all bits + eRENDERDOC_Overlay_None = 0, +} RENDERDOC_OverlayBits; + +// returns the overlay bits that have been set +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(); +// sets the overlay bits with an and & or mask +typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or); + +// this function will attempt to remove RenderDoc's hooks in the application. +// +// Note: that this can only work correctly if done immediately after +// the module is loaded, before any API work happens. RenderDoc will remove its +// injected hooks and shut down. Behaviour is undefined if this is called +// after any API functions have been called, and there is still no guarantee of +// success. +typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(); + +// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers. +typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown; + +// This function will unload RenderDoc's crash handler. +// +// If you use your own crash handler and don't want RenderDoc's handler to +// intercede, you can call this function to unload it and any unhandled +// exceptions will pass to the next handler. +typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(); + +// Sets the capture file path template +// +// pathtemplate is a UTF-8 string that gives a template for how captures will be named +// and where they will be saved. +// +// Any extension is stripped off the path, and captures are saved in the directory +// specified, and named with the filename and the frame number appended. If the +// directory does not exist it will be created, including any parent directories. +// +// If pathtemplate is NULL, the template will remain unchanged +// +// Example: +// +// SetCaptureFilePathTemplate("my_captures/example"); +// +// Capture #1 -> my_captures/example_frame123.rdc +// Capture #2 -> my_captures/example_frame456.rdc +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate); + +// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string +typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers. +typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate; +typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate; + +// returns the number of captures that have been made +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(); + +// This function returns the details of a capture, by index. New captures are added +// to the end of the list. +// +// filename will be filled with the absolute path to the capture file, as a UTF-8 string +// pathlength will be written with the length in bytes of the filename string +// timestamp will be written with the time of the capture, in seconds since the Unix epoch +// +// Any of the parameters can be NULL and they'll be skipped. +// +// The function will return 1 if the capture index is valid, or 0 if the index is invalid +// If the index is invalid, the values will be unchanged +// +// Note: when captures are deleted in the UI they will remain in this list, so the +// capture path may not exist anymore. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename, + uint32_t *pathlength, uint64_t *timestamp); + +// Sets the comments associated with a capture file. These comments are displayed in the +// UI program when opening. +// +// filePath should be a path to the capture file to add comments to. If set to NULL or "" +// the most recent capture file created made will be used instead. +// comments should be a NULL-terminated UTF-8 string to add as comments. +// +// Any existing comments will be overwritten. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath, + const char *comments); + +// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers. +// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for +// backwards compatibility with old code, it is castable either way since it's ABI compatible +// as the same function pointer type. +typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected; + +// This function will launch the Replay UI associated with the RenderDoc library injected +// into the running application. +// +// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter +// to connect to this application +// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open +// if cmdline is NULL, the command line will be empty. +// +// returns the PID of the replay UI if successful, 0 if not successful. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl, + const char *cmdline); + +// RenderDoc can return a higher version than requested if it's backwards compatible, +// this function returns the actual version returned. If a parameter is NULL, it will be +// ignored and the others will be filled out. +typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch); + +////////////////////////////////////////////////////////////////////////// +// Capturing functions +// + +// A device pointer is a pointer to the API's root handle. +// +// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc +typedef void *RENDERDOC_DevicePointer; + +// A window handle is the OS's native window handle +// +// This would be an HWND, GLXDrawable, etc +typedef void *RENDERDOC_WindowHandle; + +// A helper macro for Vulkan, where the device handle cannot be used directly. +// +// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use. +// +// Specifically, the value needed is the dispatch table pointer, which sits as the first +// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and +// indirect once. +#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst))) + +// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will +// respond to keypresses. Neither parameter can be NULL +typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// capture the next frame on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(); + +// capture the next N frames on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames); + +// When choosing either a device pointer or a window handle to capture, you can pass NULL. +// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify +// any API rendering to a specific window, or a specific API instance rendering to any window, +// or in the simplest case of one window and one API, you can just pass NULL for both. +// +// In either case, if there are two or more possible matching (device,window) pairs it +// is undefined which one will be captured. +// +// Note: for headless rendering you can pass NULL for the window handle and either specify +// a device pointer or leave it NULL as above. + +// Immediately starts capturing API calls on the specified device pointer and window handle. +// +// If there is no matching thing to capture (e.g. no supported API has been initialised), +// this will do nothing. +// +// The results are undefined (including crashes) if two captures are started overlapping, +// even on separate devices and/oror windows. +typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Returns whether or not a frame capture is currently ongoing anywhere. +// +// This will return 1 if a capture is ongoing, and 0 if there is no capture running +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(); + +// Ends capturing immediately. +// +// This will return 1 if the capture succeeded, and 0 if there was an error capturing. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Ends capturing immediately and discard any data stored without saving to disk. +// +// This will return 1 if the capture was discarded, and 0 if there was an error or no capture +// was in progress +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API versions +// + +// RenderDoc uses semantic versioning (http://semver.org/). +// +// MAJOR version is incremented when incompatible API changes happen. +// MINOR version is incremented when functionality is added in a backwards-compatible manner. +// PATCH version is incremented when backwards-compatible bug fixes happen. +// +// Note that this means the API returned can be higher than the one you might have requested. +// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned +// instead of 1.0.0. You can check this with the GetAPIVersion entry point +typedef enum RENDERDOC_Version { + eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 + eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01 + eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02 + eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00 + eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01 + eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02 + eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00 + eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00 + eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00 + eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01 +} RENDERDOC_Version; + +// API version changelog: +// +// 1.0.0 - initial release +// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered +// by keypress or TriggerCapture, instead of Start/EndFrameCapture. +// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation +// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new +// function pointer is added to the end of the struct, the original layout is identical +// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote +// replay/remote server concept in replay UI) +// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these +// are captures and not debug logging files. This is the first API version in the v1.0 +// branch. +// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be +// displayed in the UI program on load. +// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions +// which allows users to opt-in to allowing unsupported vendor extensions to function. +// Should be used at the user's own risk. +// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to +// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to +// 0xdddddddd of uninitialised buffer contents. +// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop +// capturing without saving anything to disk. +// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening + +typedef struct RENDERDOC_API_1_4_1 +{ + pRENDERDOC_GetAPIVersion GetAPIVersion; + + pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32; + pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32; + + pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32; + pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32; + + pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys; + pRENDERDOC_SetCaptureKeys SetCaptureKeys; + + pRENDERDOC_GetOverlayBits GetOverlayBits; + pRENDERDOC_MaskOverlayBits MaskOverlayBits; + + // Shutdown was renamed to RemoveHooks in 1.4.1. + // These unions allow old code to continue compiling without changes + union + { + pRENDERDOC_Shutdown Shutdown; + pRENDERDOC_RemoveHooks RemoveHooks; + }; + pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; + + // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2. + // These unions allow old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate; + // current name + pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; + }; + union + { + // deprecated name + pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate; + // current name + pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; + }; + + pRENDERDOC_GetNumCaptures GetNumCaptures; + pRENDERDOC_GetCapture GetCapture; + + pRENDERDOC_TriggerCapture TriggerCapture; + + // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1. + // This union allows old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected; + // current name + pRENDERDOC_IsTargetControlConnected IsTargetControlConnected; + }; + pRENDERDOC_LaunchReplayUI LaunchReplayUI; + + pRENDERDOC_SetActiveWindow SetActiveWindow; + + pRENDERDOC_StartFrameCapture StartFrameCapture; + pRENDERDOC_IsFrameCapturing IsFrameCapturing; + pRENDERDOC_EndFrameCapture EndFrameCapture; + + // new function in 1.1.0 + pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture; + + // new function in 1.2.0 + pRENDERDOC_SetCaptureFileComments SetCaptureFileComments; + + // new function in 1.4.0 + pRENDERDOC_DiscardFrameCapture DiscardFrameCapture; +} RENDERDOC_API_1_4_1; + +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_0; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_1; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_2; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_0; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_1; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_2; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_2_0; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_3_0; +typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_4_0; + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API entry point +// +// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available. +// +// The name is the same as the typedef - "RENDERDOC_GetAPI" +// +// This function is not thread safe, and should not be called on multiple threads at once. +// Ideally, call this once as early as possible in your application's startup, before doing +// any API work, since some configuration functionality etc has to be done also before +// initialising any APIs. +// +// Parameters: +// version is a single value from the RENDERDOC_Version above. +// +// outAPIPointers will be filled out with a pointer to the corresponding struct of function +// pointers. +// +// Returns: +// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested +// 0 - if the requested version is not supported or the arguments are invalid. +// +typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers); + +#ifdef __cplusplus +} // extern "C" +#endif From aa8b2409d97435a570e93a702870d02cb69d0302 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Sat, 10 Apr 2021 22:39:55 +1000 Subject: [PATCH 03/21] Second pass at OpenGL debayering --- data/debayer.frag | 10 +++++- data/debayer.vert | 2 ++ gl_quickpreview.c | 77 ++++++++++++++++++++++++++++++++++++++++------ process_pipeline.c | 63 +++++++++++++++++++++++++++++++++++-- quickpreview.c | 2 +- 5 files changed, 141 insertions(+), 13 deletions(-) diff --git a/data/debayer.frag b/data/debayer.frag index 75addcb..193168e 100644 --- a/data/debayer.frag +++ b/data/debayer.frag @@ -1,6 +1,8 @@ precision mediump float; uniform sampler2D texture; +// uniform sampler2D srgb_map; +uniform mat3 color_matrix; varying vec2 uv1; varying vec2 uv2; @@ -24,5 +26,11 @@ void main() { mix(pixels.yzw, pixels.xyz, is_top_left.y), is_top_left.x); - gl_FragColor = vec4(color, 0); + 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); + + gl_FragColor = vec4((color_matrix * srgb_color).bgr, 0); } diff --git a/data/debayer.vert b/data/debayer.vert index f205536..ac06880 100644 --- a/data/debayer.vert +++ b/data/debayer.vert @@ -13,6 +13,8 @@ varying vec2 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; gl_Position = vec4(transform * vec3(vert, 1), 1); diff --git a/gl_quickpreview.c b/gl_quickpreview.c index 2bc1a8f..1f48f3a 100644 --- a/gl_quickpreview.c +++ b/gl_quickpreview.c @@ -27,6 +27,9 @@ struct _GLQuickPreview { GLuint uniform_transform; GLuint uniform_pixel_size; GLuint uniform_texture; + GLuint uniform_srgb_map; + GLuint uniform_color_matrix; + GLuint srgb_texture; }; static GLuint load_shader(const char *path, GLenum type) @@ -73,6 +76,27 @@ static GLuint load_shader(const char *path, GLenum type) 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 +// }; + GLQuickPreview *gl_quick_preview_new() { GLuint frame_buffer; @@ -87,9 +111,7 @@ GLQuickPreview *gl_quick_preview_new() glAttachShader(program, frag); glBindAttribLocation(program, VERTEX_ATTRIBUTE, "vert"); - check_gl(); glBindAttribLocation(program, TEX_COORD_ATTRIBUTE, "tex_coord"); - check_gl(); glLinkProgram(program); check_gl(); @@ -108,22 +130,35 @@ GLQuickPreview *gl_quick_preview_new() 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); + GLQuickPreview *self = malloc(sizeof(GLQuickPreview)); self->frame_buffer = frame_buffer; self->program = program; self->uniform_transform = glGetUniformLocation(self->program, "transform"); - check_gl(); - self->uniform_pixel_size = glGetUniformLocation(self->program, "pixel_size"); - check_gl(); - self->uniform_texture = glGetUniformLocation(self->program, "texture"); - check_gl(); - + self->uniform_color_matrix = glGetUniformLocation(self->program, "color_matrix"); + self->uniform_srgb_map = glGetUniformLocation(self->program, "srgb_map"); + // self->srgb_texture = srgb_texture; return self; } @@ -191,6 +226,30 @@ 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(); + + if (colormatrix) + { + GLfloat transposed[9]; + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + transposed[i + j * 3] = colormatrix[j + i * 3]; + + glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, transposed); + } + else + { + static const GLfloat identity[9] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, identity); + } + glVertexAttribPointer(VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, square_vertices); check_gl(); glEnableVertexAttribArray(VERTEX_ATTRIBUTE); diff --git a/process_pipeline.c b/process_pipeline.c index af14120..360aa3c 100644 --- a/process_pipeline.c +++ b/process_pipeline.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #define TIFFTAG_FORWARDMATRIX1 50964 @@ -166,6 +168,7 @@ static EGLContext egl_context = EGL_NO_CONTEXT; // static struct buffer output_buffers[NUM_BUFFERS]; static PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA; +static PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC eglExportDMABUFImageQueryMESA; static const char * egl_get_error_str() @@ -276,6 +279,8 @@ init_gl(MPPipeline *pipeline, GdkWindow **window) eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC) eglGetProcAddress("eglExportDMABUFImageMESA"); + eglExportDMABUFImageQueryMESA = (PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC) + eglGetProcAddress("eglExportDMABUFImageQueryMESA"); // Generate textures for the buffers // GLuint textures[NUM_BUFFERS * 2]; @@ -292,7 +297,6 @@ init_gl(MPPipeline *pipeline, GdkWindow **window) // output_buffers[i].dma_fd = -1; // } - check_gl(); gl_quick_preview_state = gl_quick_preview_new(); check_gl(); @@ -310,6 +314,8 @@ 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)) { #ifdef RENDERDOC if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); @@ -323,6 +329,8 @@ process_image_for_preview(const uint8_t *image) 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(); @@ -344,7 +352,58 @@ process_image_for_preview(const uint8_t *image) surface = cairo_image_surface_create( CAIRO_FORMAT_RGB24, preview_width, preview_height); - uint8_t *pixels = cairo_image_surface_get_data(surface); + 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(); diff --git a/quickpreview.c b/quickpreview.c index f8aed4f..541b62e 100644 --- a/quickpreview.c +++ b/quickpreview.c @@ -8,7 +8,7 @@ #include /* Linear -> sRGB lookup table */ -static const int srgb[] = { +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, From 337526e9b9cbb210249779b2fa9043c7555afe7b Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Thu, 15 Apr 2021 23:14:20 +1000 Subject: [PATCH 04/21] It works! --- camera.c | 1 + camera.css | 4 - camera.glade | 2 +- camera.ui | 335 ++++++++++++++++++ config/pine64,pinephone-1.0.ini | 6 +- config/pine64,pinephone-1.1.ini | 6 +- config/pine64,pinephone-1.2.ini | 6 +- data/blit.frag | 11 + data/blit.vert | 12 + data/debayer.frag | 49 +-- data/debayer.vert | 29 +- gl_quickpreview.c | 198 ++++------- gl_utils.c | 128 +++++++ gl_utils.h | 37 +- main.c | 599 +++++++++++++++++++------------- main.h | 3 +- meson.build | 3 +- process_pipeline.c | 466 ++++++++++++------------- process_pipeline.h | 10 +- 19 files changed, 1230 insertions(+), 675 deletions(-) create mode 100644 camera.ui create mode 100644 data/blit.frag create mode 100644 data/blit.vert create mode 100644 gl_utils.c diff --git a/camera.c b/camera.c index f6fbf37..6bc3e36 100644 --- a/camera.c +++ b/camera.c @@ -607,6 +607,7 @@ mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer) /* fallthrough */ default: errno_printerr("VIDIOC_DQBUF"); + exit(1); return false; } } diff --git a/camera.css b/camera.css index e897a85..ace8c79 100644 --- a/camera.css +++ b/camera.css @@ -1,7 +1,3 @@ -.black { - background: #000000; -} - .errorbox { background: #dd0000; color: #ffffff; diff --git a/camera.glade b/camera.glade index 8fb7e67..89d7872 100644 --- a/camera.glade +++ b/camera.glade @@ -27,7 +27,7 @@ False vertical - + True False diff --git a/camera.ui b/camera.ui new file mode 100644 index 0000000..bb06696 --- /dev/null +++ b/camera.ui @@ -0,0 +1,335 @@ + + + + + 100 + 1 + 10 + + + 0 + 360 + 640 + 0 + + + 0 + + + main + + + 0 + vertical + + + 1 + 0 + + + + + 0 + 0 + + + 1 + 0 + 10 + 10 + 10 + 10 + + + 0 + 10 + ISO + + + + + 1 + control_adj + 1 + + + + + Auto + 1 + 10 + + + + + + + + + + 0 + + + 1 + + + 0 + /org/postmarketos/Megapixels/settings-symbolic.svg + + + + + + + 1 + + + 0 + /org/postmarketos/Megapixels/switch-camera.svg + + + + + + + 48 + 48 + 1 + + + 0 + /org/postmarketos/Megapixels/shutter-button.svg + + + + + + + + 1 + + + 0 + /org/postmarketos/Megapixels/folder-symbolic.svg + + + + + + + 1 + + + 0 + + + 24 + 24 + 0 + + + + + 24 + 24 + 0 + + + + + + + + + + + 0 + 0 + + + 1 + 0 + 10 + 10 + 10 + 10 + 10 + + + 1 + 0 + start + No error + + + + + gtk-close + 1 + + + + + + + + + + + + + + + + diff --git a/config/pine64,pinephone-1.0.ini b/config/pine64,pinephone-1.0.ini index cafbb77..b97fcc4 100644 --- a/config/pine64,pinephone-1.0.ini +++ b/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 diff --git a/config/pine64,pinephone-1.1.ini b/config/pine64,pinephone-1.1.ini index cafbb77..b97fcc4 100644 --- a/config/pine64,pinephone-1.1.ini +++ b/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 diff --git a/config/pine64,pinephone-1.2.ini b/config/pine64,pinephone-1.2.ini index 74ce9d2..bab05aa 100644 --- a/config/pine64,pinephone-1.2.ini +++ b/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 diff --git a/data/blit.frag b/data/blit.frag new file mode 100644 index 0000000..43ea399 --- /dev/null +++ b/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); +} diff --git a/data/blit.vert b/data/blit.vert new file mode 100644 index 0000000..b4576b8 --- /dev/null +++ b/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); +} diff --git a/data/debayer.frag b/data/debayer.frag index 193168e..94ecc50 100644 --- a/data/debayer.frag +++ b/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 = mod(pixel_coord, 2.0); + vec2 arrangement = floor(half_pixel_coord); - vec2 is_top_left = step(1.0, arrangement); + vec2 is_bottom_left = step(1.0, arrangement); - 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); + // 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 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); + // Fast SRGB estimate. See https://mimosa-pudica.net/fast-gamma/ + vec3 srgb_color = (vec3(1.138) * inversesqrt(color) - vec3(0.138)) * color; - gl_FragColor = vec4((color_matrix * srgb_color).bgr, 0); + // Slow SRGB estimate + // vec3 srgb_color = pow(color, vec3(1.0 / 2.2)); + + gl_FragColor = vec4(color_matrix * srgb_color, 0); } diff --git a/data/debayer.vert b/data/debayer.vert index ac06880..d06d511 100644 --- a/data/debayer.vert +++ b/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); } diff --git a/gl_quickpreview.c b/gl_quickpreview.c index 1f48f3a..38df81e 100644 --- a/gl_quickpreview.c +++ b/gl_quickpreview.c @@ -1,153 +1,51 @@ #include "camera.h" #include "gl_quickpreview.h" #include "gl_utils.h" -#include #include + #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; + + GLuint pixel_layout_texture_id; + uint32_t pixel_layout_texture_width; + uint32_t pixel_layout_texture_height; + MPPixelFormat pixel_layout_texture_format; }; -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 -// }; - GLQuickPreview *gl_quick_preview_new() { GLuint frame_buffer; 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; } diff --git a/gl_utils.c b/gl_utils.c new file mode 100644 index 0000000..f32bf98 --- /dev/null +++ b/gl_utils.c @@ -0,0 +1,128 @@ +#include "gl_utils.h" + +#include +#include +#include +#include + +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; +} diff --git a/gl_utils.h b/gl_utils.h index bb4c268..097c86a 100644 --- a/gl_utils.h +++ b/gl_utils.h @@ -1,34 +1,13 @@ +#pragma once + #include +#include #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(); +extern const GLfloat gl_quad_vertices[8]; +extern const GLfloat gl_quad_texcoords[8]; - 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); -} +GLuint gl_load_shader(const char *path, GLenum type); +GLuint gl_link_program(GLuint *shaders, size_t num_shaders); diff --git a/main.c b/main.c index dd825fe..3e38311 100644 --- a/main.c +++ b/main.c @@ -18,11 +18,20 @@ #include #include #include +#include "gl_utils.h" #include "camera_config.h" #include "quickpreview.h" #include "io_pipeline.h" #include "process_pipeline.h" +#define RENDERDOC + +#ifdef RENDERDOC +#include +#include +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), + }; - // 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); + blit_program = gl_link_program(blit_shaders, 2); + glBindAttribLocation(blit_program, 0, "vert"); + glBindAttribLocation(blit_program, 1, "tex_coord"); + check_gl(); - 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); -} +// 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; +// 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; - } +// 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; -} +// 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); +// 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); +// 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); +// 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)); +// 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); -} +// 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; +// 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); - } +// // 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); +// 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); - } +// } 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; - } +// 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; +// // 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]; +// 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; - } - } - } +// 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(); - } -} +// // 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; } diff --git a/main.h b/main.h index 2995993..fe899ac 100644 --- a/main.h +++ b/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); diff --git a/meson.build b/meson.build index 40f31c6..ef331a6 100644 --- a/meson.build +++ b/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', diff --git a/process_pipeline.c b/process_pipeline.c index 360aa3c..8e178ff 100644 --- a/process_pipeline.c +++ b/process_pipeline.c @@ -16,7 +16,7 @@ #include "gl_utils.h" #include #include -#include +// #include #include #include @@ -151,130 +151,100 @@ mp_process_pipeline_sync() #define NUM_BUFFERS 4 +struct _MPProcessPipelineBuffer { + GLuint texture_id; + + _Atomic(int) refcount; +}; +static MPProcessPipelineBuffer output_buffers[NUM_BUFFERS]; + +static int output_buffer_width = 0; +static int output_buffer_height = 0; + +void +mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf) +{ + ++buf->refcount; +} + +void +mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf) +{ + --buf->refcount; +} + +uint32_t +mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf) +{ + return buf->texture_id; +} + static GLQuickPreview *gl_quick_preview_state = NULL; -static EGLDisplay egl_display = EGL_NO_DISPLAY; -static EGLContext egl_context = EGL_NO_CONTEXT; - -// 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]; +static GdkGLContext *context; static PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA; static PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC eglExportDMABUFImageQueryMESA; -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"; -} +// 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"; +// } #define RENDERDOC #ifdef RENDERDOC -#include #include -RENDERDOC_API_1_1_2 *rdoc_api = NULL; +extern RENDERDOC_API_1_1_2 *rdoc_api; #endif static void -init_gl(MPPipeline *pipeline, GdkWindow **window) +init_gl(MPPipeline *pipeline, GdkSurface **surface) { -#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"); - } + 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; } + + 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 - 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); - - EGLint major, minor; - if (!eglInitialize(egl_display, &major, &minor)) { - printf("Failed to initialize egl: %s\n", egl_get_error_str()); - return; - } - - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - printf("Failed to bind OpenGL ES: %s\n", egl_get_error_str()); - return; - } - - printf("extensions: %s\n", eglQueryString(egl_display, EGL_EXTENSIONS)); - - EGLint config_attrs[1] = { - EGL_NONE - }; - - 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; - } - - if (num_configs != 1) { - printf("No egl configs found: %s\n", egl_get_error_str()); - 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; - } - - 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)) { -#ifdef RENDERDOC - if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); -#endif - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - check_gl(); + assert(gl_quick_preview_state && ql_quick_preview_supports_format(gl_quick_preview_state, mode.pixel_format)); - GLuint textures[2]; - glGenTextures(2, textures); - - 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); - -#ifdef RENDERDOC - 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); + // 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); +#endif + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + check_gl(); + + // 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(); + + + 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); +#endif + + 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); + // 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); - } + // 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() {} diff --git a/process_pipeline.h b/process_pipeline.h index 28f1b78..9d0f69b 100644 --- a/process_pipeline.h +++ b/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); From 91817b167a8f9b33ff82fc2ee46c276968411f0d Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Sun, 18 Apr 2021 23:13:56 +1000 Subject: [PATCH 05/21] Reorganization, replacing CPU debayering entirely --- camera.glade | 592 ------------------ data/blit.frag | 2 + data/blit.vert | 2 + camera.css => data/camera.css | 0 camera.ui => data/camera.ui | 1 + data/debayer.frag | 37 +- data/debayer.vert | 15 +- .../folder-symbolic.svg | 0 data/meson.build | 15 + .../org.postmarketos.Megapixels.gresource.xml | 15 + postprocess.sh => data/postprocess.sh | 0 .../settings-symbolic.svg | 0 shutter-button.svg => data/shutter-button.svg | 0 switch-camera.svg => data/switch-camera.svg | 0 gl_quickpreview.c | 207 ------ gl_quickpreview.h | 21 - gl_utils.c | 128 ---- gl_utils.h | 13 - meson.build | 90 ++- org.postmarketos.Megapixels.gresource.xml | 11 - quickpreview.c | 351 ----------- quickpreview.h | 14 - camera.c => src/camera.c | 0 camera.h => src/camera.h | 0 camera_config.c => src/camera_config.c | 0 camera_config.h => src/camera_config.h | 0 device.c => src/device.c | 0 device.h => src/device.h | 0 src/gl_util.c | 220 +++++++ src/gl_util.h | 17 + src/gles2_debayer.c | 141 +++++ src/gles2_debayer.h | 21 + ini.c => src/ini.c | 0 ini.h => src/ini.h | 0 io_pipeline.c => src/io_pipeline.c | 0 io_pipeline.h => src/io_pipeline.h | 0 main.c => src/main.c | 70 +-- main.h => src/main.h | 0 matrix.c => src/matrix.c | 0 matrix.h => src/matrix.h | 0 pipeline.c => src/pipeline.c | 0 pipeline.h => src/pipeline.h | 0 process_pipeline.c => src/process_pipeline.c | 125 ++-- process_pipeline.h => src/process_pipeline.h | 0 {renderdoc => src/renderdoc}/app.h | 0 zbar_pipeline.c => src/zbar_pipeline.c | 0 zbar_pipeline.h => src/zbar_pipeline.h | 0 tests/test_quickpreview.c | 120 ---- 48 files changed, 571 insertions(+), 1657 deletions(-) delete mode 100644 camera.glade rename camera.css => data/camera.css (100%) rename camera.ui => data/camera.ui (99%) rename folder-symbolic.svg => data/folder-symbolic.svg (100%) create mode 100644 data/meson.build create mode 100644 data/org.postmarketos.Megapixels.gresource.xml rename postprocess.sh => data/postprocess.sh (100%) rename settings-symbolic.svg => data/settings-symbolic.svg (100%) rename shutter-button.svg => data/shutter-button.svg (100%) rename switch-camera.svg => data/switch-camera.svg (100%) delete mode 100644 gl_quickpreview.c delete mode 100644 gl_quickpreview.h delete mode 100644 gl_utils.c delete mode 100644 gl_utils.h delete mode 100644 org.postmarketos.Megapixels.gresource.xml delete mode 100644 quickpreview.c delete mode 100644 quickpreview.h rename camera.c => src/camera.c (100%) rename camera.h => src/camera.h (100%) rename camera_config.c => src/camera_config.c (100%) rename camera_config.h => src/camera_config.h (100%) rename device.c => src/device.c (100%) rename device.h => src/device.h (100%) create mode 100644 src/gl_util.c create mode 100644 src/gl_util.h create mode 100644 src/gles2_debayer.c create mode 100644 src/gles2_debayer.h rename ini.c => src/ini.c (100%) rename ini.h => src/ini.h (100%) rename io_pipeline.c => src/io_pipeline.c (100%) rename io_pipeline.h => src/io_pipeline.h (100%) rename main.c => src/main.c (95%) rename main.h => src/main.h (100%) rename matrix.c => src/matrix.c (100%) rename matrix.h => src/matrix.h (100%) rename pipeline.c => src/pipeline.c (100%) rename pipeline.h => src/pipeline.h (100%) rename process_pipeline.c => src/process_pipeline.c (85%) rename process_pipeline.h => src/process_pipeline.h (100%) rename {renderdoc => src/renderdoc}/app.h (100%) rename zbar_pipeline.c => src/zbar_pipeline.c (100%) rename zbar_pipeline.h => src/zbar_pipeline.h (100%) delete mode 100644 tests/test_quickpreview.c diff --git a/camera.glade b/camera.glade deleted file mode 100644 index 89d7872..0000000 --- a/camera.glade +++ /dev/null @@ -1,592 +0,0 @@ - - - - - - 100 - 1 - 10 - - - False - Camera - 360 - 640 - - - True - False - - - True - False - vertical - - - True - False - vertical - - - True - False - - - True - True - 0 - - - - - - True - True - 0 - - - - - False - - - True - False - 10 - 10 - 10 - 10 - - - True - False - 10 - ISO - - - False - True - 0 - - - - - True - True - control_adj - 1 - False - - - True - True - 1 - - - - - Auto - True - True - True - 10 - - - False - True - end - 2 - - - - - True - True - 0 - - - - - - False - True - 1 - - - - - True - False - - - 48 - 48 - True - True - True - True - - - True - False - /org/postmarketos/Megapixels/shutter-button.svg - - - - - - False - True - 2 - - - - - True - False - 8 - 8 - 10 - - - True - True - True - - - True - False - /org/postmarketos/Megapixels/settings-symbolic.svg - - - - - False - True - 0 - - - - - True - True - True - - - True - False - /org/postmarketos/Megapixels/switch-camera.svg - - - - - False - True - 1 - - - - - - - - True - True - 10 - 0 - - - - - True - False - 8 - 8 - 10 - - - - - - True - True - True - - - True - False - /org/postmarketos/Megapixels/folder-symbolic.svg - - - - - False - True - end - 1 - - - - - True - True - True - - - True - False - - - 24 - 24 - True - False - - - - - 24 - 24 - True - False - - - - - - - False - True - end - 2 - - - - - True - True - 10 - end - 1 - - - - - False - True - 10 - end - 1 - - - - - False - - - True - False - 10 - 10 - 10 - 10 - 10 - - - True - False - start - No error - - - True - True - 0 - - - - - gtk-close - True - True - True - True - True - - - False - True - 1 - - - - - True - True - 0 - - - - - - False - True - 2 - - - - - main - page0 - - - - - True - True - in - - - True - False - none - - - True - False - 10 - 10 - 10 - 10 - vertical - 10 - - - True - False - - - Back - True - True - True - 10 - 10 - - - - False - True - 0 - - - - - True - False - Settings aren't functional yet - - - False - True - 1 - - - - - False - True - 0 - - - - - True - False - vertical - 4 - - - True - False - start - Photos - - - - False - True - 0 - - - - - True - False - 0 - in - - - True - False - 12 - - - True - False - vertical - 6 - - - True - False - start - Resolution - - - False - True - 0 - - - - - True - False - - - False - True - 1 - - - - - - - - - - - True - False - start - Storage mode - - - False - True - 4 - - - - - True - False - vertical - - - Debayer with VNG (slowest) - True - True - False - True - True - - - False - True - 0 - - - - - Debayer with linear interpolation - True - True - False - True - store_vng - - - False - True - 1 - - - - - Raw - True - True - False - True - store_vng - - - False - True - 2 - - - - - False - True - 5 - - - - - - - - - - - - - False - True - 1 - - - - - False - True - 1 - - - - - - - - - - - - settings - page1 - 1 - - - - - - diff --git a/data/blit.frag b/data/blit.frag index 43ea399..1f1b314 100644 --- a/data/blit.frag +++ b/data/blit.frag @@ -1,4 +1,6 @@ +#ifdef GL_ES precision mediump float; +#endif uniform sampler2D texture; diff --git a/data/blit.vert b/data/blit.vert index b4576b8..557c872 100644 --- a/data/blit.vert +++ b/data/blit.vert @@ -1,4 +1,6 @@ +#ifdef GL_ES precision mediump float; +#endif attribute vec2 vert; attribute vec2 tex_coord; diff --git a/camera.css b/data/camera.css similarity index 100% rename from camera.css rename to data/camera.css diff --git a/camera.ui b/data/camera.ui similarity index 99% rename from camera.ui rename to data/camera.ui index bb06696..9b137f0 100644 --- a/camera.ui +++ b/data/camera.ui @@ -25,6 +25,7 @@ 1 0 + 1 diff --git a/data/debayer.frag b/data/debayer.frag index 94ecc50..8e9f184 100644 --- a/data/debayer.frag +++ b/data/debayer.frag @@ -1,37 +1,28 @@ -precision highp float; +#ifdef GL_ES +precision mediump float; +#endif uniform sampler2D texture; -uniform sampler2D pixel_layout; uniform mat3 color_matrix; -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(top_left_uv), - fetch(top_right_uv), - fetch(bottom_right_uv), - fetch(bottom_left_uv)); + // Note the coordinates for texture samples need to be a varying, as the + // Mali-400 has this as a fast path allowing 32-bit floats. Otherwise + // they end up as 16-bit floats and that's not accurate enough. + vec4 samples = vec4( + texture2D(texture, top_left_uv).r, + texture2D(texture, top_right_uv).r, + texture2D(texture, bottom_left_uv).r, + texture2D(texture, bottom_right_uv).r); - vec2 arrangement = floor(half_pixel_coord); - - vec2 is_bottom_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; + // Assume BGGR for now. Currently this just takes 3 of the four samples + // for each pixel, there's room here to do some better debayering. + vec3 color = samples.wyx; // Fast SRGB estimate. See https://mimosa-pudica.net/fast-gamma/ vec3 srgb_color = (vec3(1.138) * inversesqrt(color) - vec3(0.138)) * color; diff --git a/data/debayer.vert b/data/debayer.vert index d06d511..49af8c1 100644 --- a/data/debayer.vert +++ b/data/debayer.vert @@ -1,20 +1,17 @@ -precision highp float; +#ifdef GL_ES +precision mediump float; +#endif 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 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() { top_left_uv = tex_coord - pixel_size / 2.0; @@ -22,11 +19,5 @@ void main() { 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); } diff --git a/folder-symbolic.svg b/data/folder-symbolic.svg similarity index 100% rename from folder-symbolic.svg rename to data/folder-symbolic.svg diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..c0de055 --- /dev/null +++ b/data/meson.build @@ -0,0 +1,15 @@ +resources = gnome.compile_resources('megapixels-resources', + 'org.postmarketos.Megapixels.gresource.xml') + +install_data(['org.postmarketos.Megapixels.desktop'], + install_dir: get_option('datadir') / 'applications') + +install_data(['org.postmarketos.Megapixels.metainfo.xml'], + install_dir: get_option('datadir') / 'metainfo') + +install_data('org.postmarketos.Megapixels.svg', + install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps')) + +install_data(['postprocess.sh'], + install_dir: get_option('datadir') / 'megapixels/', + install_mode: 'rwxr-xr-x') diff --git a/data/org.postmarketos.Megapixels.gresource.xml b/data/org.postmarketos.Megapixels.gresource.xml new file mode 100644 index 0000000..c71c8db --- /dev/null +++ b/data/org.postmarketos.Megapixels.gresource.xml @@ -0,0 +1,15 @@ + + + + camera.ui + camera.css + switch-camera.svg + shutter-button.svg + folder-symbolic.svg + settings-symbolic.svg + blit.vert + blit.frag + debayer.vert + debayer.frag + + diff --git a/postprocess.sh b/data/postprocess.sh similarity index 100% rename from postprocess.sh rename to data/postprocess.sh diff --git a/settings-symbolic.svg b/data/settings-symbolic.svg similarity index 100% rename from settings-symbolic.svg rename to data/settings-symbolic.svg diff --git a/shutter-button.svg b/data/shutter-button.svg similarity index 100% rename from shutter-button.svg rename to data/shutter-button.svg diff --git a/switch-camera.svg b/data/switch-camera.svg similarity index 100% rename from switch-camera.svg rename to data/switch-camera.svg diff --git a/gl_quickpreview.c b/gl_quickpreview.c deleted file mode 100644 index 38df81e..0000000 --- a/gl_quickpreview.c +++ /dev/null @@ -1,207 +0,0 @@ -#include "camera.h" -#include "gl_quickpreview.h" -#include "gl_utils.h" -#include - - -#define VERTEX_ATTRIBUTE 0 -#define TEX_COORD_ATTRIBUTE 1 - -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 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() -{ - GLuint frame_buffer; - glGenFramebuffers(1, &frame_buffer); - check_gl(); - - 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"); - check_gl(); - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - check_gl(); - - GLuint pixel_layout_texture_id; - glGenTextures(1, &pixel_layout_texture_id); - check_gl(); - - GLQuickPreview *self = malloc(sizeof(GLQuickPreview)); - self->frame_buffer = frame_buffer; - self->program = program; - - 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"); - 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; -} - -void gl_quick_preview_free(GLQuickPreview *self) -{ - glDeleteFramebuffers(1, &self->frame_buffer); - - free(self); -} - -bool -ql_quick_preview_supports_format(GLQuickPreview *self, const MPPixelFormat format) -{ - return format == MP_PIXEL_FMT_BGGR8; -} - -bool -gl_quick_preview(GLQuickPreview *self, - GLuint dst_id, - const uint32_t dst_width, const uint32_t dst_height, - GLuint source_id, - const uint32_t src_width, const uint32_t src_height, - const MPPixelFormat format, - const uint32_t rotation, - const bool mirrored, - 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); - glBindTexture(GL_TEXTURE_2D, dst_id); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_id, 0); - check_gl(); - - glViewport(0, 0, dst_width, dst_height); - check_gl(); - - assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); - - glUseProgram(self->program); - check_gl(); - - GLfloat rotation_list[4] = { 0, -1, 0, 1 }; - int rotation_index = 4 - rotation / 90; - - GLfloat sin_rot = rotation_list[rotation_index]; - GLfloat cos_rot = rotation_list[(rotation_index + 1) % 4]; - GLfloat scale_x = mirrored ? 1 : -1; - GLfloat matrix[9] = { - cos_rot * scale_x, sin_rot, 0, - -sin_rot * scale_x, cos_rot, 0, - 0, 0, 1, - }; - 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); - check_gl(); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, source_id); - glUniform1i(self->uniform_texture, 0); - check_gl(); - - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, self->pixel_layout_texture_id); - glUniform1i(self->uniform_pixel_layout, 1); - check_gl(); - - if (colormatrix) - { - GLfloat transposed[9]; - for (int i = 0; i < 3; ++i) - for (int j = 0; j < 3; ++j) - transposed[i + j * 3] = colormatrix[j + i * 3]; - - glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, transposed); - } - else - { - static const GLfloat identity[9] = { - 1, 0, 0, - 0, 1, 0, - 0, 0, 1, - }; - glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, identity); - } - - glVertexAttribPointer(VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, gl_quad_vertices); - glEnableVertexAttribArray(VERTEX_ATTRIBUTE); - glVertexAttribPointer(TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, gl_quad_texcoords); - glEnableVertexAttribArray(TEX_COORD_ATTRIBUTE); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - check_gl(); - - // TODO: Render - // glClearColor(0, 0, 1, 0); - // glClear(GL_COLOR_BUFFER_BIT); - - // glBindTexture(GL_TEXTURE_2D, 0); - // glBindFramebuffer(GL_FRAMEBUFFER, 0); - - return true; -} diff --git a/gl_quickpreview.h b/gl_quickpreview.h deleted file mode 100644 index fede8c5..0000000 --- a/gl_quickpreview.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "quickpreview.h" -#include -#include -#include - -typedef struct _GLQuickPreview GLQuickPreview; - -GLQuickPreview* gl_quick_preview_new(); -void gl_quick_preview_free(GLQuickPreview *self); - -bool ql_quick_preview_supports_format(GLQuickPreview *self, const MPPixelFormat format); -bool gl_quick_preview(GLQuickPreview *self, - GLuint dst_id, - const uint32_t dst_width, const uint32_t dst_height, - GLuint source_id, - const uint32_t src_width, const uint32_t src_height, - const MPPixelFormat format, - const uint32_t rotation, - const bool mirrored, - const float *colormatrix, - const uint8_t blacklevel); diff --git a/gl_utils.c b/gl_utils.c deleted file mode 100644 index f32bf98..0000000 --- a/gl_utils.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "gl_utils.h" - -#include -#include -#include -#include - -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; -} diff --git a/gl_utils.h b/gl_utils.h deleted file mode 100644 index 097c86a..0000000 --- a/gl_utils.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include - -#define check_gl() __check_gl(__FILE__, __LINE__) -void __check_gl(const char *file, int line); - -extern const GLfloat gl_quad_vertices[8]; -extern const GLfloat gl_quad_texcoords[8]; - -GLuint gl_load_shader(const char *path, GLenum type); -GLuint gl_link_program(GLuint *shaders, size_t num_shaders); diff --git a/meson.build b/meson.build index ef331a6..163fc20 100644 --- a/meson.build +++ b/meson.build @@ -1,78 +1,76 @@ project('megapixels', 'c') + gnome = import('gnome') gtkdep = dependency('gtk4') tiff = dependency('libtiff-4') zbar = dependency('zbar') threads = dependency('threads') -gl = dependency('gl') -egl = dependency('egl') +# gl = dependency('gl') +epoxy = dependency('epoxy') cc = meson.get_compiler('c') libm = cc.find_library('m', required: false) -resources = gnome.compile_resources('megapixels-resources', 'org.postmarketos.Megapixels.gresource.xml') +subdir('data') conf = configuration_data() conf.set_quoted('DATADIR', join_paths(get_option('prefix'), get_option('datadir'))) conf.set_quoted('SYSCONFDIR', get_option('sysconfdir')) configure_file( output: 'config.h', - configuration: conf ) + configuration: conf) # Define DEBUG for debug builds only (debugoptimized is not included on this one) if get_option('buildtype') == 'debug' add_global_arguments('-DDEBUG', language: 'c') endif -# Workaround for libtiff having ABI changes but not changing the internal version number +# Workaround for libtiff having ABI changes but not changing the internal +# version number if get_option('tiffcfapattern') add_global_arguments('-DLIBTIFF_CFA_PATTERN', language: 'c') endif executable('megapixels', - 'main.c', - 'ini.c', - 'quickpreview.c', - 'gl_quickpreview.c', - 'gl_utils.c', - 'camera.c', - 'device.c', - 'pipeline.c', - 'camera_config.c', - 'io_pipeline.c', - 'process_pipeline.c', - 'zbar_pipeline.c', - 'matrix.c', - resources, - dependencies : [gtkdep, libm, tiff, zbar, threads, gl, egl], - install : true, - link_args : '-Wl,-ldl') + 'src/main.c', + 'src/ini.c', + 'src/gles2_debayer.c', + 'src/gl_util.c', + 'src/camera.c', + 'src/device.c', + 'src/pipeline.c', + 'src/camera_config.c', + 'src/io_pipeline.c', + 'src/process_pipeline.c', + 'src/zbar_pipeline.c', + 'src/matrix.c', + resources, + include_directories: 'src/', + dependencies: [gtkdep, libm, tiff, zbar, threads, epoxy], + install: true, + link_args: '-Wl,-ldl') -install_data(['data/org.postmarketos.Megapixels.desktop'], - install_dir : get_option('datadir') / 'applications') - -install_data(['data/org.postmarketos.Megapixels.metainfo.xml'], - install_dir : get_option('datadir') / 'metainfo') - -install_data('data/org.postmarketos.Megapixels.svg', - install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps') -) - -install_data([ - 'config/pine64,pinephone-1.0.ini', - 'config/pine64,pinephone-1.1.ini', - 'config/pine64,pinephone-1.2.ini', - 'config/pine64,pinetab.ini', +install_data( + [ + 'config/pine64,pinephone-1.0.ini', + 'config/pine64,pinephone-1.1.ini', + 'config/pine64,pinephone-1.2.ini', + 'config/pine64,pinetab.ini', ], - install_dir : get_option('datadir') / 'megapixels/config/') - -install_data(['postprocess.sh'], - install_dir : get_option('datadir') / 'megapixels/', - install_mode: 'rwxr-xr-x') + install_dir: get_option('datadir') / 'megapixels/config/') # Tools -executable('megapixels-list-devices', 'tools/list_devices.c', 'device.c', dependencies: [gtkdep], install: true) -executable('megapixels-camera-test', 'tools/camera_test.c', 'camera.c', 'device.c', dependencies: [gtkdep], install: true) +executable('megapixels-list-devices', + 'tools/list_devices.c', + 'src/device.c', + include_directories: 'src/', + dependencies: [gtkdep], + install: true) -test_quickpreview = executable('test_quickpreview', 'tests/test_quickpreview.c', 'quickpreview.c', 'camera.c', dependencies: [gtkdep]) -test('quickpreview', test_quickpreview) +executable('megapixels-camera-test', + 'tools/camera_test.c', + 'src/camera.c', + 'src/device.c', + include_directories: 'src/', + dependencies: [gtkdep], + install: true) diff --git a/org.postmarketos.Megapixels.gresource.xml b/org.postmarketos.Megapixels.gresource.xml deleted file mode 100644 index d9938c1..0000000 --- a/org.postmarketos.Megapixels.gresource.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - camera.glade - camera.css - switch-camera.svg - shutter-button.svg - folder-symbolic.svg - settings-symbolic.svg - - diff --git a/quickpreview.c b/quickpreview.c deleted file mode 100644 index 541b62e..0000000 --- a/quickpreview.c +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Fast but bad debayer method that scales and rotates by skipping source - * pixels and doesn't interpolate any values at all - */ - -#include "quickpreview.h" -#include -#include - -/* Linear -> sRGB lookup table */ -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 -}; - -static inline uint32_t -pack_rgb(uint8_t r, uint8_t g, uint8_t b) -{ - return (r << 16) | (g << 8) | b; -} - -static inline uint32_t -convert_yuv_to_srgb(uint8_t y, uint8_t u, uint8_t v) -{ - uint32_t r = 1.164f * y + 1.596f * (v - 128); - uint32_t g = 1.164f * y - 0.813f * (v - 128) - 0.391f * (u - 128); - uint32_t b = 1.164f * y + 2.018f * (u - 128); - return pack_rgb(r, g, b); -} - -static inline uint32_t -apply_colormatrix(uint32_t color, const float *colormatrix) -{ - if (!colormatrix) { - return color; - } - - uint32_t r = (color >> 16) * colormatrix[0] + - ((color >> 8) & 0xFF) * colormatrix[1] + - (color & 0xFF) * colormatrix[2]; - uint32_t g = (color >> 16) * colormatrix[3] + - ((color >> 8) & 0xFF) * colormatrix[4] + - (color & 0xFF) * colormatrix[5]; - uint32_t b = (color >> 16) * colormatrix[6] + - ((color >> 8) & 0xFF) * colormatrix[7] + - (color & 0xFF) * colormatrix[8]; - - // Clip colors - if (r > 0xFF) - r = 0xFF; - if (g > 0xFF) - g = 0xFF; - if (b > 0xFF) - b = 0xFF; - return pack_rgb(r, g, b); -} - -static inline uint32_t -coord_map(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int rotation, - bool mirrored) -{ - uint32_t x_r, y_r; - if (rotation == 0) { - x_r = x; - y_r = y; - } else if (rotation == 90) { - x_r = y; - y_r = height - x - 1; - } else if (rotation == 270) { - x_r = width - y - 1; - y_r = x; - } else { - x_r = width - x - 1; - y_r = height - y - 1; - } - - if (mirrored) { - x_r = width - x_r - 1; - } - - uint32_t index = y_r * width + x_r; -#ifdef DEBUG - assert(index < width * height); -#endif - return index; -} - -static void -quick_preview_rggb8(uint32_t *dst, const uint32_t dst_width, - const uint32_t dst_height, const uint8_t *src, - const uint32_t src_width, const uint32_t src_height, - const MPPixelFormat format, const uint32_t rotation, - const bool mirrored, const float *colormatrix, - const uint8_t blacklevel, const uint32_t skip) -{ - uint32_t src_y = 0, dst_y = 0; - while (src_y < src_height) { - uint32_t src_x = 0, dst_x = 0; - while (src_x < src_width) { - uint32_t src_i = src_y * src_width + src_x; - - uint8_t b0 = srgb[src[src_i] - blacklevel]; - uint8_t b1 = srgb[src[src_i + 1] - blacklevel]; - uint8_t b2 = srgb[src[src_i + src_width + 1] - blacklevel]; - - uint32_t color; - switch (format) { - case MP_PIXEL_FMT_BGGR8: - color = pack_rgb(b2, b1, b0); - break; - case MP_PIXEL_FMT_GBRG8: - color = pack_rgb(b2, b0, b1); - break; - case MP_PIXEL_FMT_GRBG8: - color = pack_rgb(b1, b0, b2); - break; - case MP_PIXEL_FMT_RGGB8: - color = pack_rgb(b0, b1, b2); - break; - default: - assert(false); - } - - color = apply_colormatrix(color, colormatrix); - - dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation, - mirrored)] = color; - - src_x += 2 + 2 * skip; - ++dst_x; - } - - src_y += 2 + 2 * skip; - ++dst_y; - } -} - -static void -quick_preview_rggb10(uint32_t *dst, const uint32_t dst_width, - const uint32_t dst_height, const uint8_t *src, - const uint32_t src_width, const uint32_t src_height, - const MPPixelFormat format, const uint32_t rotation, - const bool mirrored, const float *colormatrix, - const uint8_t blacklevel, const uint32_t skip) -{ - assert(src_width % 2 == 0); - - uint32_t width_bytes = mp_pixel_format_width_to_bytes(format, src_width); - - uint32_t src_y = 0, dst_y = 0; - while (src_y < src_height) { - uint32_t src_x = 0, dst_x = 0; - while (src_x < width_bytes) { - uint32_t src_i = src_y * width_bytes + src_x; - - uint8_t b0 = srgb[src[src_i] - blacklevel]; - uint8_t b1 = srgb[src[src_i + 1] - blacklevel]; - uint8_t b2 = srgb[src[src_i + width_bytes + 1] - blacklevel]; - - uint32_t color; - switch (format) { - case MP_PIXEL_FMT_BGGR10P: - color = pack_rgb(b2, b1, b0); - break; - case MP_PIXEL_FMT_GBRG10P: - color = pack_rgb(b2, b0, b1); - break; - case MP_PIXEL_FMT_GRBG10P: - color = pack_rgb(b1, b0, b2); - break; - case MP_PIXEL_FMT_RGGB10P: - color = pack_rgb(b0, b1, b2); - break; - default: - assert(false); - } - - color = apply_colormatrix(color, colormatrix); - - dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation, - mirrored)] = color; - - uint32_t advance = 1 + skip; - if (src_x % 5 == 0) { - src_x += 2 * (advance % 2) + 5 * (advance / 2); - } else { - src_x += 3 * (advance % 2) + 5 * (advance / 2); - } - ++dst_x; - } - - src_y += 2 + 2 * skip; - ++dst_y; - } -} - -static void -quick_preview_yuv(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height, - const uint8_t *src, const uint32_t src_width, - const uint32_t src_height, const MPPixelFormat format, - const uint32_t rotation, const bool mirrored, - const float *colormatrix, const uint32_t skip) -{ - assert(src_width % 2 == 0); - - uint32_t width_bytes = src_width * 2; - - uint32_t unrot_dst_width = dst_width; - if (rotation != 0 && rotation != 180) { - unrot_dst_width = dst_height; - } - - uint32_t src_y = 0, dst_y = 0; - while (src_y < src_height) { - uint32_t src_x = 0, dst_x = 0; - while (src_x < width_bytes) { - uint32_t src_i = src_y * width_bytes + src_x; - - uint8_t b0 = src[src_i]; - uint8_t b1 = src[src_i + 1]; - uint8_t b2 = src[src_i + 2]; - uint8_t b3 = src[src_i + 3]; - - uint32_t color1, color2; - switch (format) { - case MP_PIXEL_FMT_UYVY: - color1 = convert_yuv_to_srgb(b1, b0, b2); - color2 = convert_yuv_to_srgb(b3, b0, b2); - break; - case MP_PIXEL_FMT_YUYV: - color1 = convert_yuv_to_srgb(b0, b1, b3); - color2 = convert_yuv_to_srgb(b2, b1, b3); - break; - default: - assert(false); - } - - color1 = apply_colormatrix(color1, colormatrix); - color2 = apply_colormatrix(color2, colormatrix); - - uint32_t dst_i1 = coord_map(dst_x, dst_y, dst_width, - dst_height, rotation, mirrored); - dst[dst_i1] = color1; - ++dst_x; - - // The last pixel needs to be skipped if we have an odd un-rotated width - if (dst_x < unrot_dst_width) { - uint32_t dst_i2 = - coord_map(dst_x, dst_y, dst_width, - dst_height, rotation, mirrored); - dst[dst_i2] = color2; - ++dst_x; - } - - src_x += 4 + 4 * skip; - } - - src_y += 1 + skip; - ++dst_y; - } -} - -void -quick_preview(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height, - const uint8_t *src, const uint32_t src_width, - const uint32_t src_height, const MPPixelFormat format, - const uint32_t rotation, const bool mirrored, const float *colormatrix, - const uint8_t blacklevel, const uint32_t skip) -{ - switch (format) { - case MP_PIXEL_FMT_BGGR8: - case MP_PIXEL_FMT_GBRG8: - case MP_PIXEL_FMT_GRBG8: - case MP_PIXEL_FMT_RGGB8: - quick_preview_rggb8(dst, dst_width, dst_height, src, src_width, - src_height, format, rotation, mirrored, - colormatrix, blacklevel, skip); - break; - case MP_PIXEL_FMT_BGGR10P: - case MP_PIXEL_FMT_GBRG10P: - case MP_PIXEL_FMT_GRBG10P: - case MP_PIXEL_FMT_RGGB10P: - quick_preview_rggb10(dst, dst_width, dst_height, src, src_width, - src_height, format, rotation, mirrored, - colormatrix, blacklevel, skip); - break; - case MP_PIXEL_FMT_UYVY: - case MP_PIXEL_FMT_YUYV: - quick_preview_yuv(dst, dst_width, dst_height, src, src_width, - src_height, format, rotation, mirrored, - colormatrix, skip); - break; - default: - assert(false); - } -} - -static uint32_t -div_ceil(uint32_t x, uint32_t y) -{ - return x / y + !!(x % y); -} - -void -quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip, - const uint32_t preview_width, const uint32_t preview_height, - const uint32_t src_width, const uint32_t src_height, - const MPPixelFormat format, const int rotation) -{ - uint32_t colors_x = mp_pixel_format_width_to_colors(format, src_width); - uint32_t colors_y = mp_pixel_format_height_to_colors(format, src_height); - - if (rotation != 0 && rotation != 180) { - uint32_t tmp = colors_x; - colors_x = colors_y; - colors_y = tmp; - } - - uint32_t scale_x = colors_x / preview_width; - uint32_t scale_y = colors_y / preview_height; - - if (scale_x > 0) - --scale_x; - if (scale_y > 0) - --scale_y; - *skip = scale_x > scale_y ? scale_x : scale_y; - - *dst_width = div_ceil(colors_x, (1 + *skip)); - if (*dst_width <= 0) - *dst_width = 1; - - *dst_height = div_ceil(colors_y, (1 + *skip)); - if (*dst_height <= 0) - *dst_height = 1; -} diff --git a/quickpreview.h b/quickpreview.h deleted file mode 100644 index 1815215..0000000 --- a/quickpreview.h +++ /dev/null @@ -1,14 +0,0 @@ -#include "camera.h" -#include - -void quick_preview(uint32_t *dst, const uint32_t dst_width, - const uint32_t dst_height, const uint8_t *src, - const uint32_t src_width, const uint32_t src_height, - const MPPixelFormat format, const uint32_t rotation, - const bool mirrored, const float *colormatrix, - const uint8_t blacklevel, const uint32_t skip); - -void quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip, - const uint32_t preview_width, const uint32_t preview_height, - const uint32_t src_width, const uint32_t src_height, - const MPPixelFormat format, const int rotation); diff --git a/camera.c b/src/camera.c similarity index 100% rename from camera.c rename to src/camera.c diff --git a/camera.h b/src/camera.h similarity index 100% rename from camera.h rename to src/camera.h diff --git a/camera_config.c b/src/camera_config.c similarity index 100% rename from camera_config.c rename to src/camera_config.c diff --git a/camera_config.h b/src/camera_config.h similarity index 100% rename from camera_config.h rename to src/camera_config.h diff --git a/device.c b/src/device.c similarity index 100% rename from device.c rename to src/device.c diff --git a/device.h b/src/device.h similarity index 100% rename from device.h rename to src/device.h diff --git a/src/gl_util.c b/src/gl_util.c new file mode 100644 index 0000000..c4c76ca --- /dev/null +++ b/src/gl_util.c @@ -0,0 +1,220 @@ +#include "gl_util.h" + +#include +#include +#include +#include +#include +#include +#include + +void gl_util_check_error(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); + + // raise(SIGTRAP); +} + +GLuint +gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra) +{ + GdkGLContext *context = gdk_gl_context_get_current(); + assert(context); + + GLuint shader = glCreateShader(type); + if (shader == 0) { + return 0; + } + + GBytes *bytes = g_resources_lookup_data(resource, 0, NULL); + if (!bytes) { + printf("Failed to load shader resource %s\n", resource); + return 0; + } + + // Build #define for OpenGL context information + gboolean is_es = gdk_gl_context_get_use_es(context); + int major, minor; + gdk_gl_context_get_version(context, &major, &minor); + char context_info_buf[128]; + snprintf(context_info_buf, 128, "#define %s\n#define GL_%d\n#define GL_%d_%d\n", is_es ? "GL_ES" : "GL_NO_ES", major, major, minor); + + gsize glib_size = 0; + const GLchar *source = g_bytes_get_data(bytes, &glib_size); + if (glib_size == 0 || glib_size > INT_MAX) { + printf("Invalid size for resource\n"); + return 0; + } + + const GLchar **sources = malloc((num_extra + 1) * sizeof(GLchar *)); + GLint *sizes = malloc((num_extra + 1) * sizeof(GLint)); + + for (size_t i = 0; i < num_extra; ++i) { + sources[i] = extra_sources[i]; + sizes[i] = -1; + } + sources[num_extra] = source; + sizes[num_extra] = glib_size; + + glShaderSource(shader, num_extra + 1, sources, sizes); + glCompileShader(shader); + check_gl(); + + free(sources); + free(sizes); + + g_bytes_unref(bytes); + + // Check compile status + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (success == GL_FALSE) { + printf("Shader compilation failed for %s\n", resource); + + glDeleteShader(shader); + return 0; + } + + 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", resource, log); + free(log); + + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint +gl_util_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; +} + +static const GLfloat quad_data[] = { + // Vertices + -1, -1, + 1, -1, + -1, 1, + 1, 1, + // Texcoords + 0, 0, + 1, 0, + 0, 1, + 1, 1, +}; + +GLuint gl_util_new_quad() +{ + GdkGLContext *context = gdk_gl_context_get_current(); + assert(context); + + if (gdk_gl_context_get_use_es(context)) { + return 0; + } else { + GLuint buffer; + glGenBuffers(1, &buffer); + + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STATIC_DRAW); + check_gl(); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + check_gl(); + + return buffer; + } +} + +void gl_util_bind_quad(GLuint buffer) +{ + GdkGLContext *context = gdk_gl_context_get_current(); + assert(context); + + if (gdk_gl_context_get_use_es(context)) { + glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data); + check_gl(); + glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE); + check_gl(); + + glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data + 8); + check_gl(); + glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE); + check_gl(); + } else { + glBindBuffer(GL_ARRAY_BUFFER, buffer); + check_gl(); + + glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE); + check_gl(); + + glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, (void*) (8 * sizeof(float))); + glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE); + check_gl(); + } +} + +void gl_util_draw_quad(GLuint buffer) +{ + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + check_gl(); +} diff --git a/src/gl_util.h b/src/gl_util.h new file mode 100644 index 0000000..f1e8451 --- /dev/null +++ b/src/gl_util.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#define GL_UTIL_VERTEX_ATTRIBUTE 0 +#define GL_UTIL_TEX_COORD_ATTRIBUTE 1 + +#define check_gl() gl_util_check_error(__FILE__, __LINE__) +void gl_util_check_error(const char *file, int line); + +GLuint gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra); +GLuint gl_util_link_program(GLuint *shaders, size_t num_shaders); + +GLuint gl_util_new_quad(); +void gl_util_bind_quad(GLuint buffer); +void gl_util_draw_quad(GLuint buffer); diff --git a/src/gles2_debayer.c b/src/gles2_debayer.c new file mode 100644 index 0000000..df31fc4 --- /dev/null +++ b/src/gles2_debayer.c @@ -0,0 +1,141 @@ +#include "gles2_debayer.h" + +#include "camera.h" +#include "gl_util.h" +#include + +#define VERTEX_ATTRIBUTE 0 +#define TEX_COORD_ATTRIBUTE 1 + +struct _GLES2Debayer { + GLuint frame_buffer; + GLuint program; + GLuint uniform_transform; + GLuint uniform_pixel_size; + GLuint uniform_texture; + GLuint uniform_color_matrix; + + GLuint quad; +}; + +GLES2Debayer * +gles2_debayer_new(MPPixelFormat format) +{ + if (format != MP_PIXEL_FMT_BGGR8) { + return NULL; + } + + GLuint frame_buffer; + glGenFramebuffers(1, &frame_buffer); + check_gl(); + + GLuint shaders[] = { + gl_util_load_shader("/org/postmarketos/Megapixels/debayer.vert", GL_VERTEX_SHADER, NULL, 0), + gl_util_load_shader("/org/postmarketos/Megapixels/debayer.frag", GL_FRAGMENT_SHADER, NULL, 0), + }; + + GLuint program = gl_util_link_program(shaders, 2); + glBindAttribLocation(program, VERTEX_ATTRIBUTE, "vert"); + glBindAttribLocation(program, TEX_COORD_ATTRIBUTE, "tex_coord"); + check_gl(); + + GLES2Debayer *self = malloc(sizeof(GLES2Debayer)); + self->frame_buffer = frame_buffer; + self->program = program; + + self->uniform_transform = glGetUniformLocation(self->program, "transform"); + self->uniform_pixel_size = glGetUniformLocation(self->program, "pixel_size"); + self->uniform_texture = glGetUniformLocation(self->program, "texture"); + self->uniform_color_matrix = glGetUniformLocation(self->program, "color_matrix"); + check_gl(); + + self->quad = gl_util_new_quad(); + + return self; +} + +void +gles2_debayer_free(GLES2Debayer *self) +{ + glDeleteFramebuffers(1, &self->frame_buffer); + + glDeleteProgram(self->program); + + free(self); +} + +void +gles2_debayer_use(GLES2Debayer *self) +{ + glUseProgram(self->program); + check_gl(); + + gl_util_bind_quad(self->quad); +} + +void +gles2_debayer_configure(GLES2Debayer *self, + const uint32_t dst_width, const uint32_t dst_height, + const uint32_t src_width, const uint32_t src_height, + const uint32_t rotation, + const bool mirrored, + const float *colormatrix, + const uint8_t blacklevel) +{ + glViewport(0, 0, dst_width, dst_height); + check_gl(); + + GLfloat rotation_list[4] = { 0, -1, 0, 1 }; + int rotation_index = 4 - rotation / 90; + + GLfloat sin_rot = rotation_list[rotation_index]; + GLfloat cos_rot = rotation_list[(rotation_index + 1) % 4]; + GLfloat scale_x = mirrored ? 1 : -1; + GLfloat matrix[9] = { + cos_rot * scale_x, sin_rot, 0, + -sin_rot * scale_x, cos_rot, 0, + 0, 0, 1, + }; + glUniformMatrix3fv(self->uniform_transform, 1, GL_FALSE, matrix); + 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); + check_gl(); + + if (colormatrix) { + GLfloat transposed[9]; + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + transposed[i + j * 3] = colormatrix[j + i * 3]; + + glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, transposed); + } else { + static const GLfloat identity[9] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, identity); + } + check_gl(); +} + +void +gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id) +{ + glBindFramebuffer(GL_FRAMEBUFFER, self->frame_buffer); + glBindTexture(GL_TEXTURE_2D, dst_id); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_id, 0); + check_gl(); + + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, source_id); + glUniform1i(self->uniform_texture, 0); + check_gl(); + + gl_util_draw_quad(self->quad); +} diff --git a/src/gles2_debayer.h b/src/gles2_debayer.h new file mode 100644 index 0000000..0028749 --- /dev/null +++ b/src/gles2_debayer.h @@ -0,0 +1,21 @@ +#include "camera.h" +#include "gl_util.h" +#include +#include + +typedef struct _GLES2Debayer GLES2Debayer; + +GLES2Debayer* gles2_debayer_new(MPPixelFormat format); +void gles2_debayer_free(GLES2Debayer *self); + +void gles2_debayer_use(GLES2Debayer *self); + +void gles2_debayer_configure(GLES2Debayer *self, + const uint32_t dst_width, const uint32_t dst_height, + const uint32_t src_width, const uint32_t src_height, + const uint32_t rotation, + const bool mirrored, + const float *colormatrix, + const uint8_t blacklevel); + +void gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id); diff --git a/ini.c b/src/ini.c similarity index 100% rename from ini.c rename to src/ini.c diff --git a/ini.h b/src/ini.h similarity index 100% rename from ini.h rename to src/ini.h diff --git a/io_pipeline.c b/src/io_pipeline.c similarity index 100% rename from io_pipeline.c rename to src/io_pipeline.c diff --git a/io_pipeline.h b/src/io_pipeline.h similarity index 100% rename from io_pipeline.h rename to src/io_pipeline.h diff --git a/main.c b/src/main.c similarity index 95% rename from main.c rename to src/main.c index 3e38311..fb3af54 100644 --- a/main.c +++ b/src/main.c @@ -18,9 +18,8 @@ #include #include #include -#include "gl_utils.h" +#include "gl_util.h" #include "camera_config.h" -#include "quickpreview.h" #include "io_pipeline.h" #include "process_pipeline.h" @@ -330,6 +329,7 @@ draw_controls() static GLuint blit_program; static GLuint blit_uniform_texture; +static GLuint quad; static void preview_realize(GtkGLArea *area) @@ -340,17 +340,27 @@ preview_realize(GtkGLArea *area) return; } + // Make a VAO for OpenGL + if (!gtk_gl_area_get_use_es(area)) { + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + check_gl(); + } + GLuint blit_shaders[] = { - gl_load_shader("data/blit.vert", GL_VERTEX_SHADER), - gl_load_shader("data/blit.frag", GL_FRAGMENT_SHADER), + gl_util_load_shader("/org/postmarketos/Megapixels/blit.vert", GL_VERTEX_SHADER, NULL, 0), + gl_util_load_shader("/org/postmarketos/Megapixels/blit.frag", GL_FRAGMENT_SHADER, NULL, 0), }; - blit_program = gl_link_program(blit_shaders, 2); - glBindAttribLocation(blit_program, 0, "vert"); - glBindAttribLocation(blit_program, 1, "tex_coord"); + blit_program = gl_util_link_program(blit_shaders, 2); + glBindAttribLocation(blit_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert"); + glBindAttribLocation(blit_program, GL_UTIL_TEX_COORD_ATTRIBUTE, "tex_coord"); check_gl(); blit_uniform_texture = glGetUniformLocation(blit_program, "texture"); + + quad = gl_util_new_quad(); } static gboolean @@ -364,9 +374,9 @@ preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) return FALSE; } -// #ifdef RENDERDOC -// if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); -// #endif +#ifdef RENDERDOC + if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); +#endif glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); @@ -377,13 +387,10 @@ preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) 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(); + + gl_util_bind_quad(quad); + gl_util_draw_quad(quad); } /* @@ -433,9 +440,9 @@ preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) glFlush(); -// #ifdef RENDERDOC -// if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL); -// #endif +#ifdef RENDERDOC + if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL); +#endif return FALSE; } @@ -758,6 +765,9 @@ on_realize(GtkWidget *window, gpointer *data) { GtkNative *native = gtk_widget_get_native(window); mp_process_pipeline_init_gl(gtk_native_get_surface(native)); + + camera = mp_get_camera_config(0); + update_io_pipeline(); } typedef struct @@ -778,13 +788,10 @@ startup(GApplication *app) g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); - 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"); - } + assert(g_resources_lookup_data("/org/postmarketos/Megapixels/camera.ui", 0, NULL) != NULL); + + GtkBuilder *builder = gtk_builder_new_from_resource( + "/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")); @@ -842,12 +849,8 @@ startup(GApplication *app) G_CALLBACK(on_control_slider_changed), NULL); GtkCssProvider *provider = gtk_css_provider_new(); - if (access("camera.css", F_OK) != -1) { - gtk_css_provider_load_from_path(provider, "camera.css"); - } else { - gtk_css_provider_load_from_resource( - provider, "/org/postmarketos/Megapixels/camera.css"); - } + gtk_css_provider_load_from_resource( + provider, "/org/postmarketos/Megapixels/camera.css"); GtkStyleContext *context = gtk_widget_get_style_context(error_box); gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); @@ -868,9 +871,6 @@ startup(GApplication *app) 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); } diff --git a/main.h b/src/main.h similarity index 100% rename from main.h rename to src/main.h diff --git a/matrix.c b/src/matrix.c similarity index 100% rename from matrix.c rename to src/matrix.c diff --git a/matrix.h b/src/matrix.h similarity index 100% rename from matrix.h rename to src/matrix.h diff --git a/pipeline.c b/src/pipeline.c similarity index 100% rename from pipeline.c rename to src/pipeline.c diff --git a/pipeline.h b/src/pipeline.h similarity index 100% rename from pipeline.h rename to src/pipeline.h diff --git a/process_pipeline.c b/src/process_pipeline.c similarity index 85% rename from process_pipeline.c rename to src/process_pipeline.c index 8e178ff..e6dc53a 100644 --- a/process_pipeline.c +++ b/src/process_pipeline.c @@ -5,18 +5,14 @@ #include "io_pipeline.h" #include "main.h" #include "config.h" -#include "quickpreview.h" -#include "gl_quickpreview.h" +#include "gles2_debayer.h" #include #include #include #include #include -#include "gl_utils.h" -#include -#include -// #include +#include "gl_util.h" #include #include @@ -82,9 +78,9 @@ find_processor(char *script) wordfree(&exp_result); // Check postprocess.h in the current working directory - sprintf(script, "%s", filename); + sprintf(script, "data/%s", filename); if (access(script, F_OK) != -1) { - sprintf(script, "./%s", filename); + sprintf(script, "./data/%s", filename); printf("Found postprocessor script at %s\n", script); return true; } @@ -158,9 +154,6 @@ struct _MPProcessPipelineBuffer { }; static MPProcessPipelineBuffer output_buffers[NUM_BUFFERS]; -static int output_buffer_width = 0; -static int output_buffer_height = 0; - void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf) { @@ -179,37 +172,10 @@ mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf) return buf->texture_id; } -static GLQuickPreview *gl_quick_preview_state = NULL; +static GLES2Debayer *gles2_debayer = NULL; static GdkGLContext *context; -static PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA; -static PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC eglExportDMABUFImageQueryMESA; - -// 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"; -// } - #define RENDERDOC #ifdef RENDERDOC @@ -230,6 +196,7 @@ init_gl(MPPipeline *pipeline, GdkSurface **surface) gdk_gl_context_set_use_es(context, true); gdk_gl_context_set_required_version(context, 2, 0); + gdk_gl_context_set_forward_compatible(context, false); #ifdef DEBUG gdk_gl_context_set_debug_enabled(context, true); #else @@ -247,29 +214,22 @@ init_gl(MPPipeline *pipeline, GdkSurface **surface) gdk_gl_context_make_current(context); check_gl(); - eglExportDMABUFImageMESA = (PFNEGLEXPORTDMABUFIMAGEMESAPROC) - eglGetProcAddress("eglExportDMABUFImageMESA"); - eglExportDMABUFImageQueryMESA = (PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC) - eglGetProcAddress("eglExportDMABUFImageQueryMESA"); + // Make a VAO for OpenGL + if (!gdk_gl_context_get_use_es(context)) { + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + check_gl(); + } - // Generate textures for the buffers - // GLuint textures[NUM_BUFFERS * 2]; - // glGenTextures(NUM_BUFFERS * 2, textures); - - // for (size_t i = 0; i < NUM_BUFFERS; ++i) { - // input_buffers[i].texture_id = textures[i]; - // input_buffers[i].egl_image = EGL_NO_IMAGE; - // input_buffers[i].dma_fd = -1; - // } - // for (size_t i = 0; i < NUM_BUFFERS; ++i) { - // output_buffers[i].texture_id = textures[NUM_BUFFERS + i]; - // output_buffers[i].egl_image = EGL_NO_IMAGE; - // output_buffers[i].dma_fd = -1; - // } - - gl_quick_preview_state = gl_quick_preview_new(); + gles2_debayer = gles2_debayer_new(MP_PIXEL_FMT_BGGR8); check_gl(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + check_gl(); + + gles2_debayer_use(gles2_debayer); + 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); @@ -279,7 +239,11 @@ init_gl(MPPipeline *pipeline, GdkSurface **surface) glBindTexture(GL_TEXTURE_2D, 0); - printf("Initialized OpenGL\n"); + gboolean is_es = gdk_gl_context_get_use_es(context); + int major, minor; + gdk_gl_context_get_version(context, &major, &minor); + + printf("Initialized %s %d.%d\n", is_es ? "OpenGL ES" : "OpenGL", major, minor); } void @@ -293,8 +257,6 @@ process_image_for_preview(const uint8_t *image) { clock_t t1 = clock(); - 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) { @@ -311,8 +273,6 @@ process_image_for_preview(const uint8_t *image) #ifdef RENDERDOC if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); #endif - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - check_gl(); // Copy image to a GL texture. TODO: This can be avoided GLuint input_texture; @@ -325,24 +285,13 @@ process_image_for_preview(const uint8_t *image) glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, mode.width, mode.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image); check_gl(); - - 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); + gles2_debayer_process( + gles2_debayer, output_buffer->texture_id, input_texture); 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); + glDeleteTextures(1, &input_texture); clock_t t2 = clock(); printf("%fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000); @@ -727,10 +676,10 @@ mp_process_pipeline_capture() } static void -update_output_buffers() +on_output_changed() { - output_buffer_width = mode.width / 2; - output_buffer_height = mode.height / 2; + int output_buffer_width = mode.width / 2; + int output_buffer_height = mode.height / 2; if (camera->rotate != 0 || camera->rotate != 180) { int tmp = output_buffer_width; @@ -744,12 +693,20 @@ update_output_buffers() } glBindTexture(GL_TEXTURE_2D, 0); + + gles2_debayer_configure( + gles2_debayer, + output_buffer_width, output_buffer_height, + mode.width, mode.height, + camera->rotate, camera->mirrored, + camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix, + camera->blacklevel); } 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); + const bool output_changed = (!mp_camera_mode_is_equivalent(&mode, &state->mode) || preview_width != state->preview_width || preview_height != state->preview_height); camera = state->camera; mode = state->mode; @@ -766,8 +723,8 @@ 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(); + if (output_changed) { + on_output_changed(); } struct mp_main_state main_state = { diff --git a/process_pipeline.h b/src/process_pipeline.h similarity index 100% rename from process_pipeline.h rename to src/process_pipeline.h diff --git a/renderdoc/app.h b/src/renderdoc/app.h similarity index 100% rename from renderdoc/app.h rename to src/renderdoc/app.h diff --git a/zbar_pipeline.c b/src/zbar_pipeline.c similarity index 100% rename from zbar_pipeline.c rename to src/zbar_pipeline.c diff --git a/zbar_pipeline.h b/src/zbar_pipeline.h similarity index 100% rename from zbar_pipeline.h rename to src/zbar_pipeline.h diff --git a/tests/test_quickpreview.c b/tests/test_quickpreview.c deleted file mode 100644 index 9b43778..0000000 --- a/tests/test_quickpreview.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "quickpreview.h" -#include -#include -#include - -static void -test_quick_preview_fuzz() -{ - for (size_t i = 0; i < 10000; ++i) { - uint32_t width = rand() % 127 + 1; - uint32_t height = rand() % 127 + 1; - uint32_t format = rand() % (MP_PIXEL_FMT_MAX - 1) + 1; - - assert(width > 0 && height > 0); - - switch (format) { - case MP_PIXEL_FMT_BGGR8: - case MP_PIXEL_FMT_GBRG8: - case MP_PIXEL_FMT_GRBG8: - case MP_PIXEL_FMT_RGGB8: - width += width % 2; - height += height % 2; - break; - case MP_PIXEL_FMT_BGGR10P: - case MP_PIXEL_FMT_GBRG10P: - case MP_PIXEL_FMT_GRBG10P: - case MP_PIXEL_FMT_RGGB10P: - width += width % 2; - height += height % 2; - break; - case MP_PIXEL_FMT_UYVY: - case MP_PIXEL_FMT_YUYV: - width += width % 4; - break; - default: - assert(false); - } - - int rotation; - switch (rand() % 3) { - case 0: - rotation = 0; - break; - case 1: - rotation = 90; - break; - case 2: - rotation = 180; - break; - default: - rotation = 270; - break; - } - - bool mirrored = rand() % 2; - - float matbuf[9]; - float *colormatrix = NULL; - if (rand() % 2) { - for (int j = 0; j < 9; ++j) { - matbuf[j] = (double)rand() / RAND_MAX * 2.0; - } - colormatrix = matbuf; - } - - uint8_t blacklevel = rand() % 3; - - size_t src_size = mp_pixel_format_width_to_bytes(format, width) * height; - uint8_t *src = malloc(src_size); - for (int j = 0; j < src_size; ++j) { - src[j] = rand(); - } - - uint32_t preview_width = mp_pixel_format_width_to_colors(format, width); - uint32_t preview_height = mp_pixel_format_height_to_colors(format, height); - if (preview_width > 32 && preview_height > 32) { - preview_width /= (1 + rand() % 2); - preview_height /= (1 + rand() % 2); - } - - uint32_t dst_width, dst_height, skip; - quick_preview_size( - &dst_width, - &dst_height, - &skip, - preview_width, - preview_height, - width, - height, - format, - rotation); - - uint32_t *dst = malloc(dst_width * dst_height * sizeof(uint32_t)); - - quick_preview( - dst, - dst_width, - dst_height, - src, - width, - height, - format, - rotation, - mirrored, - colormatrix, - blacklevel, - skip); - - free(dst); - free(src); - } -} - -int -main(int argc, char *argv[]) -{ - g_test_init(&argc, &argv, NULL); - g_test_add_func("/quick_preview/fuzz", test_quick_preview_fuzz); - return g_test_run(); -} From d42ee3e1c031383fe96276386a294ef87a501d0f Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Tue, 20 Apr 2021 00:04:53 +1000 Subject: [PATCH 06/21] Progress on GTK4 port --- data/camera.ui | 225 ++++++++++++++--------------------------- src/main.c | 211 ++++++++++++++++++-------------------- src/main.h | 3 + src/process_pipeline.c | 9 +- 4 files changed, 182 insertions(+), 266 deletions(-) diff --git a/data/camera.ui b/data/camera.ui index 9b137f0..1feddc8 100644 --- a/data/camera.ui +++ b/data/camera.ui @@ -18,9 +18,8 @@ main - + 0 - vertical 1 @@ -28,7 +27,7 @@ 1 - + 0 0 @@ -68,38 +67,53 @@ - - + + + horizontal + 1 + end + 0 0 - - - 1 + 5 + + + horizontal + 1 + end + 0 + 5 + 5 - - 0 - /org/postmarketos/Megapixels/settings-symbolic.svg + + 1 + + + 0 + /org/postmarketos/Megapixels/settings-symbolic.svg + + + + + + + 1 + + + 0 + /org/postmarketos/Megapixels/switch-camera.svg + + - - - 1 - - - 0 - /org/postmarketos/Megapixels/switch-camera.svg - - - - - - - 48 - 48 + + + app.capture 1 + 60 0 /org/postmarketos/Megapixels/shutter-button.svg @@ -109,35 +123,46 @@ - - - 1 + + + horizontal + 1 + end + end + 0 + 5 + 5 - - 0 - /org/postmarketos/Megapixels/folder-symbolic.svg - - - - - - - 1 - - - 0 + + 1 - - 24 - 24 + 0 + /org/postmarketos/Megapixels/folder-symbolic.svg + + + + + 1 - - 24 - 24 + 0 + + + 24 + 24 + 0 + + + + + 24 + 24 + 0 + + @@ -146,7 +171,7 @@ - + 0 0 @@ -184,18 +209,15 @@ - + diff --git a/src/main.c b/src/main.c index fb3af54..faf58ff 100644 --- a/src/main.c +++ b/src/main.c @@ -51,6 +51,8 @@ static bool has_auto_focus_continuous; static bool has_auto_focus_start; static MPProcessPipelineBuffer *current_preview_buffer = NULL; +static int preview_buffer_width = -1; +static int preview_buffer_height = -1; static cairo_surface_t *status_surface = NULL; static char last_path[260] = ""; @@ -130,6 +132,9 @@ update_state(const struct mp_main_state *state) has_auto_focus_start = state->has_auto_focus_start; } + preview_buffer_width = state->image_width; + preview_buffer_height = state->image_height; + return false; } @@ -381,6 +386,12 @@ preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); + double ratio = preview_buffer_height / (double)preview_buffer_width; + glViewport(0, + preview_height - preview_width * ratio, + preview_width, + preview_width * ratio); + if (current_preview_buffer) { glUseProgram(blit_program); @@ -488,18 +499,18 @@ on_open_directory_clicked(GtkWidget *widget, gpointer user_data) } void -on_shutter_clicked(GtkWidget *widget, gpointer user_data) +run_capture_action(GSimpleAction *action, GVariant *param, gpointer user_data) { gtk_spinner_start(GTK_SPINNER(process_spinner)); gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner); mp_io_pipeline_capture(); } -// void -// on_capture_shortcut(void) -// { -// on_shutter_clicked(NULL, NULL); -// } +void +run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app) +{ + g_application_quit(app); +} // static bool // check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y) @@ -584,69 +595,66 @@ on_shutter_clicked(GtkWidget *widget, gpointer user_data) // gtk_widget_destroy(dialog); // } -// void -// on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data) -// { -// if (event->type != GDK_BUTTON_PRESS) -// return; +void +preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y) +{ + // 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); + // } -// // 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); -// 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); + // } -// } 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; + // } -// 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; -// // 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]; -// 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; + // } + // } + // } -// 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(); -// } -// } + // 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) @@ -770,21 +778,9 @@ on_realize(GtkWidget *window, gpointer *data) update_io_pipeline(); } -typedef struct -{ - GtkApplication parent_instance; -} MegapixelsApp; - -typedef GtkApplicationClass MegapixelsAppClass; - -GType megapixels_app_get_type (void); -G_DEFINE_TYPE(MegapixelsApp, megapixels_app, GTK_TYPE_APPLICATION) - static void -startup(GApplication *app) +activate(GtkApplication *app, gpointer data) { - G_APPLICATION_CLASS(megapixels_app_parent_class)->startup(app); - g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); @@ -794,7 +790,6 @@ startup(GApplication *app) "/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")); GtkWidget *switch_btn = GTK_WIDGET(gtk_builder_get_object(builder, "switch_camera")); GtkWidget *settings_btn = @@ -820,8 +815,6 @@ startup(GApplication *app) 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(shutter, "clicked", G_CALLBACK(on_shutter_clicked), NULL); g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked), NULL); g_signal_connect(switch_btn, "clicked", G_CALLBACK(on_camera_switch_clicked), @@ -834,15 +827,14 @@ startup(GApplication *app) NULL); g_signal_connect(open_directory, "clicked", G_CALLBACK(on_open_directory_clicked), NULL); + 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(preview, "resize", G_CALLBACK(preview_resize), NULL); + GtkGesture *click = gtk_gesture_click_new(); + g_signal_connect(click, "pressed", G_CALLBACK(preview_pressed), NULL); + gtk_widget_add_controller(preview, GTK_EVENT_CONTROLLER(click)); + g_signal_connect(control_auto, "toggled", G_CALLBACK(on_control_auto_toggled), NULL); g_signal_connect(control_slider, "value-changed", @@ -858,46 +850,35 @@ startup(GApplication *app) 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); + // Setup capture action + GSimpleAction *capture_action = g_simple_action_new("capture", NULL); + g_signal_connect(capture_action, "activate", G_CALLBACK(run_capture_action), NULL); + g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(capture_action)); - // GtkAccelGroup* accel_group = gtk_accel_group_new(); - // gtk_accel_group_connect(accel_group, - // GDK_KEY_space, - // 0, - // 0, - // capture_shortcut); + const char *capture_accels[] = { "space", NULL }; + gtk_application_set_accels_for_action(app, "app.capture", capture_accels); - // gtk_window_add_accel_group(GTK_WINDOW(window), accel_group); + // Setup quit action + GSimpleAction *quit_action = g_simple_action_new("quit", NULL); + g_signal_connect(quit_action, "activate", G_CALLBACK(run_quit_action), app); + g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(quit_action)); + + const char *quit_accels[] = { "q", "w", NULL }; + gtk_application_set_accels_for_action(app, "app.quit", quit_accels); mp_io_pipeline_start(); - gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(window)); + gtk_application_add_window(app, GTK_WINDOW(window)); gtk_widget_show(window); } static void -shutdown(GApplication *app) +shutdown(GApplication *app, gpointer data) { // 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 @@ -924,10 +905,10 @@ main(int argc, char *argv[]) setenv("LC_NUMERIC", "C", 1); - MegapixelsApp *app = g_object_new( - megapixels_app_get_type(), - "application-id", "org.postmarketos.Megapixels", - NULL); + GtkApplication *app = gtk_application_new("org.postmarketos.Megapixels", 0); + + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), NULL); g_application_run(G_APPLICATION(app), argc, argv); diff --git a/src/main.h b/src/main.h index fe899ac..720b4c5 100644 --- a/src/main.h +++ b/src/main.h @@ -11,6 +11,9 @@ struct mp_main_state { const struct mp_camera_config *camera; MPCameraMode mode; + int image_width; + int image_height; + bool gain_is_manual; int gain; int gain_max; diff --git a/src/process_pipeline.c b/src/process_pipeline.c index e6dc53a..b9aba03 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -675,11 +675,14 @@ mp_process_pipeline_capture() mp_pipeline_invoke(pipeline, capture, NULL, 0); } +static int output_buffer_width = -1; +static int output_buffer_height = -1; + static void on_output_changed() { - int output_buffer_width = mode.width / 2; - int output_buffer_height = mode.height / 2; + output_buffer_width = mode.width / 2; + output_buffer_height = mode.height / 2; if (camera->rotate != 0 || camera->rotate != 180) { int tmp = output_buffer_width; @@ -730,6 +733,8 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state struct mp_main_state main_state = { .camera = camera, .mode = mode, + .image_width = output_buffer_width, + .image_height = output_buffer_height, .gain_is_manual = state->gain_is_manual, .gain = gain, .gain_max = gain_max, From ea0fd24cc87aab5d3afe17318fa9a9d1d2207386 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Tue, 20 Apr 2021 23:50:26 +1000 Subject: [PATCH 07/21] More progress on GTK4 port --- data/camera.css | 4 ++ data/camera.ui | 43 +++++++----- .../org.postmarketos.Megapixels.gresource.xml | 4 +- ...button.svg => shutter-button-symbolic.svg} | 0 ...-camera.svg => switch-camera-symbolic.svg} | 0 src/main.c | 70 ++++++++----------- 6 files changed, 61 insertions(+), 60 deletions(-) rename data/{shutter-button.svg => shutter-button-symbolic.svg} (100%) rename data/{switch-camera.svg => switch-camera-symbolic.svg} (100%) diff --git a/data/camera.css b/data/camera.css index ace8c79..316209d 100644 --- a/data/camera.css +++ b/data/camera.css @@ -6,3 +6,7 @@ .controlbox { background: rgba(0,0,0,0.2); } + +.button-overlay { + opacity: 0.9; +} diff --git a/data/camera.ui b/data/camera.ui index 1feddc8..5701182 100644 --- a/data/camera.ui +++ b/data/camera.ui @@ -68,7 +68,7 @@ - + horizontal 1 end @@ -84,23 +84,25 @@ 5 5 - + + app.open-settings 1 0 - /org/postmarketos/Megapixels/settings-symbolic.svg + settings-symbolic - + + app.switch-camera 1 0 - /org/postmarketos/Megapixels/switch-camera.svg + switch-camera-symbolic @@ -115,7 +117,7 @@ 60 0 - /org/postmarketos/Megapixels/shutter-button.svg + shutter-button-symbolic diff --git a/data/org.postmarketos.Megapixels.gresource.xml b/data/org.postmarketos.Megapixels.gresource.xml index c71c8db..0ad1a11 100644 --- a/data/org.postmarketos.Megapixels.gresource.xml +++ b/data/org.postmarketos.Megapixels.gresource.xml @@ -3,8 +3,8 @@ camera.ui camera.css - switch-camera.svg - shutter-button.svg + switch-camera-symbolic.svg + shutter-button-symbolic.svg folder-symbolic.svg settings-symbolic.svg blit.vert diff --git a/data/shutter-button.svg b/data/shutter-button-symbolic.svg similarity index 100% rename from data/shutter-button.svg rename to data/shutter-button-symbolic.svg diff --git a/data/switch-camera.svg b/data/switch-camera-symbolic.svg similarity index 100% rename from data/switch-camera.svg rename to data/switch-camera-symbolic.svg diff --git a/src/main.c b/src/main.c index faf58ff..9009b7d 100644 --- a/src/main.c +++ b/src/main.c @@ -473,7 +473,7 @@ preview_resize(GtkWidget *widget, int width, int height, gpointer data) } void -on_open_last_clicked(GtkWidget *widget, gpointer user_data) +run_open_last_action(GSimpleAction *action, GVariant *param, gpointer user_data) { char uri[275]; GError *error = NULL; @@ -488,7 +488,7 @@ on_open_last_clicked(GtkWidget *widget, gpointer user_data) } void -on_open_directory_clicked(GtkWidget *widget, gpointer user_data) +run_open_photos_action(GSimpleAction *action, GVariant *param, gpointer user_data) { char uri[270]; GError *error = NULL; @@ -663,7 +663,7 @@ on_error_close_clicked(GtkWidget *widget, gpointer user_data) } void -on_camera_switch_clicked(GtkWidget *widget, gpointer user_data) +run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_data) { size_t next_index = camera->index + 1; const struct mp_camera_config *next_camera = @@ -679,7 +679,7 @@ on_camera_switch_clicked(GtkWidget *widget, gpointer user_data) } void -on_settings_btn_clicked(GtkWidget *widget, gpointer user_data) +run_open_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data) { gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings"); } @@ -778,30 +778,39 @@ on_realize(GtkWidget *window, gpointer *data) update_io_pipeline(); } +static GSimpleAction *create_simple_action(GtkApplication *app, const char *name, GCallback callback) +{ + GSimpleAction *action = g_simple_action_new(name, NULL); + g_signal_connect(action, "activate", callback, NULL); + g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(action)); + return action; +} + static void activate(GtkApplication *app, gpointer data) { g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); - assert(g_resources_lookup_data("/org/postmarketos/Megapixels/camera.ui", 0, NULL) != NULL); + GdkDisplay *display = gdk_display_get_default(); + GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(display); + gtk_icon_theme_add_resource_path(icon_theme, "/org/postmarketos/Megapixels"); + + GtkCssProvider *provider = gtk_css_provider_new(); + gtk_css_provider_load_from_resource( + provider, "/org/postmarketos/Megapixels/camera.css"); + gtk_style_context_add_provider_for_display( + display, GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); GtkBuilder *builder = gtk_builder_new_from_resource( "/org/postmarketos/Megapixels/camera.ui"); GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); - GtkWidget *switch_btn = - GTK_WIDGET(gtk_builder_get_object(builder, "switch_camera")); - GtkWidget *settings_btn = - GTK_WIDGET(gtk_builder_get_object(builder, "settings")); GtkWidget *settings_back = GTK_WIDGET(gtk_builder_get_object(builder, "settings_back")); GtkWidget *error_close = GTK_WIDGET(gtk_builder_get_object(builder, "error_close")); - GtkWidget *open_last = - GTK_WIDGET(gtk_builder_get_object(builder, "open_last")); - GtkWidget *open_directory = - GTK_WIDGET(gtk_builder_get_object(builder, "open_directory")); preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview")); error_box = GTK_WIDGET(gtk_builder_get_object(builder, "error_box")); error_message = GTK_WIDGET(gtk_builder_get_object(builder, "error_message")); @@ -817,16 +826,8 @@ activate(GtkApplication *app, gpointer data) g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL); g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked), NULL); - g_signal_connect(switch_btn, "clicked", G_CALLBACK(on_camera_switch_clicked), - NULL); - g_signal_connect(settings_btn, "clicked", - G_CALLBACK(on_settings_btn_clicked), NULL); g_signal_connect(settings_back, "clicked", G_CALLBACK(on_back_clicked), NULL); - g_signal_connect(open_last, "clicked", G_CALLBACK(on_open_last_clicked), - NULL); - g_signal_connect(open_directory, "clicked", - G_CALLBACK(on_open_directory_clicked), NULL); g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL); g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL); @@ -840,29 +841,18 @@ activate(GtkApplication *app, gpointer data) g_signal_connect(control_slider, "value-changed", G_CALLBACK(on_control_slider_changed), NULL); - GtkCssProvider *provider = gtk_css_provider_new(); - gtk_css_provider_load_from_resource( - provider, "/org/postmarketos/Megapixels/camera.css"); - GtkStyleContext *context = gtk_widget_get_style_context(error_box); - gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), - GTK_STYLE_PROVIDER_PRIORITY_USER); - context = gtk_widget_get_style_context(control_box); - gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), - GTK_STYLE_PROVIDER_PRIORITY_USER); - - // Setup capture action - GSimpleAction *capture_action = g_simple_action_new("capture", NULL); - g_signal_connect(capture_action, "activate", G_CALLBACK(run_capture_action), NULL); - g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(capture_action)); + // Setup actions + create_simple_action(app, "capture", G_CALLBACK(run_capture_action)); + create_simple_action(app, "camera-switch", G_CALLBACK(run_camera_switch_action)); + create_simple_action(app, "open-settings", G_CALLBACK(run_open_settings_action)); + create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action)); + create_simple_action(app, "open-photos", G_CALLBACK(run_open_photos_action)); + create_simple_action(app, "quit", G_CALLBACK(run_quit_action)); + // Setup shortcuts const char *capture_accels[] = { "space", NULL }; gtk_application_set_accels_for_action(app, "app.capture", capture_accels); - // Setup quit action - GSimpleAction *quit_action = g_simple_action_new("quit", NULL); - g_signal_connect(quit_action, "activate", G_CALLBACK(run_quit_action), app); - g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(quit_action)); - const char *quit_accels[] = { "q", "w", NULL }; gtk_application_set_accels_for_action(app, "app.quit", quit_accels); From f9e6576266bca945e27ea38b0f8cb0f80804bc1a Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Thu, 22 Apr 2021 01:41:23 +1000 Subject: [PATCH 08/21] Progress on controls --- data/camera.ui | 239 ++++++++---------- data/controls-popover.ui | 42 +++ .../org.postmarketos.Megapixels.gresource.xml | 2 + data/shutter-symbolic.svg | 32 +++ src/main.c | 199 ++++++++++----- 5 files changed, 306 insertions(+), 208 deletions(-) create mode 100644 data/controls-popover.ui create mode 100644 data/shutter-symbolic.svg diff --git a/data/camera.ui b/data/camera.ui index 5701182..9a3cc76 100644 --- a/data/camera.ui +++ b/data/camera.ui @@ -1,11 +1,6 @@ - - 100 - 1 - 10 - 0 360 @@ -18,7 +13,7 @@ main - + 0 @@ -27,101 +22,66 @@ 1 - - - 0 - 0 - - - 1 - 0 - 10 - 10 - 10 - 10 - - - 0 - 10 - ISO - - - - - 1 - control_adj - 1 - - - - - Auto - 1 - 10 - - - - - - - - horizontal + vertical + 1 1 - end - 0 + 5 0 + 5 + 5 5 + 5 horizontal 1 - end + start 0 - 5 5 - - app.open-settings - 1 - - - 0 - settings-symbolic - - + + start + ISO + - - app.switch-camera - 1 - - - 0 - switch-camera-symbolic - - + + start + shutter-symbolic + - app.capture - 1 + 0 + start - - 60 - 0 - shutter-button-symbolic + + horizontal + 10 + + + Error + + + + + dialog-warning-symbolic + + @@ -129,43 +89,80 @@ horizontal 1 - end + 1 end 0 - 5 - 5 - - app.open-last - 1 + + horizontal + 1 + end + 0 + 5 - - 0 - - - 24 - 24 - 0 - - - - - 24 - 24 - 0 - - + + app.open-settings + settings-symbolic + + + + + app.switch-camera + switch-camera-symbolic - app.open-photos - 1 + app.capture + 60 0 + shutter-button-symbolic + + + + + + + + horizontal + 1 + end + end + 0 + 5 + + + app.open-last + + + 0 + + + 24 + 24 + 0 + + + + + 24 + 24 + 0 + + + + + + + + + app.open-photos folder-symbolic @@ -178,40 +175,6 @@ - - - 0 - 0 - - - 1 - 0 - 10 - 10 - 10 - 10 - 10 - - - 1 - 0 - start - No error - - - - - gtk-close - 1 - - - - - - - @@ -221,7 +184,7 @@ settings page1 - + 0 @@ -238,11 +201,11 @@ 0 - + Back - 1 10 10 + app.close-settings @@ -251,7 +214,7 @@ 0 - Settings aren't functional yet + Settings aren't functional yet diff --git a/data/controls-popover.ui b/data/controls-popover.ui new file mode 100644 index 0000000..140ef1a --- /dev/null +++ b/data/controls-popover.ui @@ -0,0 +1,42 @@ + + + + + + + vertical + 1 + 5 + + + + + + + horizontal + 1 + 5 + + + horizontal + 0 + 150 + + + + + + + + + center + auto + 5 + + + + + + + + diff --git a/data/org.postmarketos.Megapixels.gresource.xml b/data/org.postmarketos.Megapixels.gresource.xml index 0ad1a11..c6b7076 100644 --- a/data/org.postmarketos.Megapixels.gresource.xml +++ b/data/org.postmarketos.Megapixels.gresource.xml @@ -2,11 +2,13 @@ camera.ui + controls-popover.ui camera.css switch-camera-symbolic.svg shutter-button-symbolic.svg folder-symbolic.svg settings-symbolic.svg + shutter-symbolic.svg blit.vert blit.frag debayer.vert diff --git a/data/shutter-symbolic.svg b/data/shutter-symbolic.svg new file mode 100644 index 0000000..bcfec02 --- /dev/null +++ b/data/shutter-symbolic.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/src/main.c b/src/main.c index 9009b7d..76aff6a 100644 --- a/src/main.c +++ b/src/main.c @@ -61,12 +61,8 @@ static MPZBarScanResult *zbar_result = NULL; static int burst_length = 3; -static enum user_control current_control; - // Widgets GtkWidget *preview; -GtkWidget *error_box; -GtkWidget *error_message; GtkWidget *main_stack; GtkWidget *open_last_stack; GtkWidget *thumb_last; @@ -598,39 +594,6 @@ run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app) void preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y) { - // 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 @@ -656,13 +619,7 @@ preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y) } } -void -on_error_close_clicked(GtkWidget *widget, gpointer user_data) -{ - gtk_widget_hide(error_box); -} - -void +static void run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_data) { size_t next_index = camera->index + 1; @@ -678,19 +635,132 @@ run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_d update_io_pipeline(); } -void +static void run_open_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data) { gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings"); } -void -on_back_clicked(GtkWidget *widget, gpointer user_data) +static void +run_close_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data) { gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main"); } -void +static void +on_controls_scale_changed(GtkAdjustment *adjustment, void (*set_fn)(double)) +{ + set_fn(gtk_adjustment_get_value(adjustment)); +} + +static void +update_value(GtkAdjustment *adjustment, GtkLabel *label) +{ + char buf[12]; + snprintf(buf, 12, "%.0f", gtk_adjustment_get_value(adjustment)); + gtk_label_set_label(label, buf); +} + +static void +on_auto_controls_toggled(GtkToggleButton *button, void (*set_auto_fn)(bool)) +{ + set_auto_fn(gtk_toggle_button_get_active(button)); +} + +static void +update_scale(GtkToggleButton *button, GtkScale *scale) +{ + gtk_widget_set_sensitive(GTK_WIDGET(scale), !gtk_toggle_button_get_active(button)); +} + +static void +open_controls(GtkWidget *parent, const char *title_name, + double min_value, double max_value, double current, + bool auto_enabled, + void (*set_fn)(double), + void (*set_auto_fn)(bool)) +{ + GtkBuilder *builder = gtk_builder_new_from_resource( + "/org/postmarketos/Megapixels/controls-popover.ui"); + GtkPopover *popover = GTK_POPOVER(gtk_builder_get_object(builder, "controls")); + GtkScale *scale = GTK_SCALE(gtk_builder_get_object(builder, "scale")); + GtkLabel *title = GTK_LABEL(gtk_builder_get_object(builder, "title")); + GtkLabel *value_label = GTK_LABEL(gtk_builder_get_object(builder, "value-label")); + GtkToggleButton *auto_button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "auto-button")); + + gtk_label_set_label(title, title_name); + + GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(scale)); + gtk_adjustment_set_lower(adjustment, min_value); + gtk_adjustment_set_upper(adjustment, max_value); + gtk_adjustment_set_value(adjustment, current); + update_value(adjustment, value_label); + + gtk_toggle_button_set_active(auto_button, auto_enabled); + update_scale(auto_button, scale); + + g_signal_connect(adjustment, "value-changed", G_CALLBACK(on_controls_scale_changed), set_fn); + g_signal_connect(adjustment, "value-changed", G_CALLBACK(update_value), value_label); + g_signal_connect(auto_button, "toggled", G_CALLBACK(on_auto_controls_toggled), set_auto_fn); + g_signal_connect(auto_button, "toggled", G_CALLBACK(update_scale), scale); + + gtk_widget_set_parent(GTK_WIDGET(popover), parent); + gtk_popover_popup(popover); + // g_object_unref(popover); +} + +static void +set_gain(double value) +{ + if (gain != (int)value) { + gain = value; + update_io_pipeline(); + } +} + +static void +set_gain_auto(bool is_auto) +{ + if (gain_is_manual != !is_auto) { + gain_is_manual = !is_auto; + update_io_pipeline(); + } +} + +static void +open_iso_controls(GtkWidget *button, gpointer user_data) +{ + open_controls(button, "ISO", 0, gain_max, gain, !gain_is_manual, set_gain, set_gain_auto); +} + +static void +set_shutter(double value) +{ + int new_exposure = + (int)(value / 360.0 * camera->capture_mode.height); + if (new_exposure != exposure) { + exposure = new_exposure; + update_io_pipeline(); + } +} + +static void +set_shutter_auto(bool is_auto) +{ + if (exposure_is_manual != !is_auto) { + exposure_is_manual = !is_auto; + update_io_pipeline(); + } +} + +static void +open_shutter_controls(GtkWidget *button, gpointer user_data) +{ + open_controls(button, "Shutter", 1.0, 360.0, exposure, !exposure_is_manual, set_shutter, set_shutter_auto); +} + +/* +static void on_control_auto_toggled(GtkToggleButton *widget, gpointer user_data) { bool is_manual = gtk_toggle_button_get_active(widget) ? false : true; @@ -737,7 +807,7 @@ on_control_auto_toggled(GtkToggleButton *widget, gpointer user_data) } } -void +static void on_control_slider_changed(GtkAdjustment *widget, gpointer user_data) { double value = gtk_adjustment_get_value(widget); @@ -767,6 +837,7 @@ on_control_slider_changed(GtkAdjustment *widget, gpointer user_data) draw_controls(); } } +*/ static void on_realize(GtkWidget *window, gpointer *data) @@ -778,10 +849,11 @@ on_realize(GtkWidget *window, gpointer *data) update_io_pipeline(); } -static GSimpleAction *create_simple_action(GtkApplication *app, const char *name, GCallback callback) +static GSimpleAction * +create_simple_action(GtkApplication *app, const char *name, GCallback callback) { GSimpleAction *action = g_simple_action_new(name, NULL); - g_signal_connect(action, "activate", callback, NULL); + g_signal_connect(action, "activate", callback, app); g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(action)); return action; } @@ -807,27 +879,15 @@ activate(GtkApplication *app, gpointer data) "/org/postmarketos/Megapixels/camera.ui"); GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); - GtkWidget *settings_back = - GTK_WIDGET(gtk_builder_get_object(builder, "settings_back")); - GtkWidget *error_close = - GTK_WIDGET(gtk_builder_get_object(builder, "error_close")); + GtkWidget *iso_button = GTK_WIDGET(gtk_builder_get_object(builder, "iso-controls-button")); + GtkWidget *shutter_button = GTK_WIDGET(gtk_builder_get_object(builder, "shutter-controls-button")); preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview")); - error_box = GTK_WIDGET(gtk_builder_get_object(builder, "error_box")); - error_message = GTK_WIDGET(gtk_builder_get_object(builder, "error_message")); main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack")); open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack")); thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last")); process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner")); - control_box = GTK_WIDGET(gtk_builder_get_object(builder, "control_box")); - control_name = GTK_WIDGET(gtk_builder_get_object(builder, "control_name")); - control_slider = - 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(error_close, "clicked", G_CALLBACK(on_error_close_clicked), - NULL); - g_signal_connect(settings_back, "clicked", G_CALLBACK(on_back_clicked), - NULL); g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL); g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL); @@ -836,15 +896,14 @@ activate(GtkApplication *app, gpointer data) g_signal_connect(click, "pressed", G_CALLBACK(preview_pressed), NULL); gtk_widget_add_controller(preview, GTK_EVENT_CONTROLLER(click)); - g_signal_connect(control_auto, "toggled", - G_CALLBACK(on_control_auto_toggled), NULL); - g_signal_connect(control_slider, "value-changed", - G_CALLBACK(on_control_slider_changed), NULL); + g_signal_connect(iso_button, "clicked", G_CALLBACK(open_iso_controls), NULL); + g_signal_connect(shutter_button, "clicked", G_CALLBACK(open_shutter_controls), NULL); // Setup actions create_simple_action(app, "capture", G_CALLBACK(run_capture_action)); create_simple_action(app, "camera-switch", G_CALLBACK(run_camera_switch_action)); create_simple_action(app, "open-settings", G_CALLBACK(run_open_settings_action)); + create_simple_action(app, "close-settings", G_CALLBACK(run_close_settings_action)); create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action)); create_simple_action(app, "open-photos", G_CALLBACK(run_open_photos_action)); create_simple_action(app, "quit", G_CALLBACK(run_quit_action)); From 44fc390b992f301525ab831c3ab03763a1bf346d Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Mon, 26 Apr 2021 18:09:04 +1000 Subject: [PATCH 09/21] Get zbar working again --- data/blit.frag | 4 +- .../org.postmarketos.Megapixels.gresource.xml | 2 + data/solid.frag | 9 + data/solid.vert | 9 + src/main.c | 416 ++++++++---------- src/process_pipeline.c | 5 +- src/zbar_pipeline.c | 125 +++++- src/zbar_pipeline.h | 13 +- 8 files changed, 329 insertions(+), 254 deletions(-) create mode 100644 data/solid.frag create mode 100644 data/solid.vert diff --git a/data/blit.frag b/data/blit.frag index 1f1b314..58df011 100644 --- a/data/blit.frag +++ b/data/blit.frag @@ -6,8 +6,6 @@ uniform sampler2D texture; varying vec2 uv; -#define fetch(p) texture2D(texture, p).r - void main() { - gl_FragColor = texture2D(texture, uv); + gl_FragColor = vec4(texture2D(texture, uv).rgb, 1); } diff --git a/data/org.postmarketos.Megapixels.gresource.xml b/data/org.postmarketos.Megapixels.gresource.xml index c6b7076..0243024 100644 --- a/data/org.postmarketos.Megapixels.gresource.xml +++ b/data/org.postmarketos.Megapixels.gresource.xml @@ -11,6 +11,8 @@ shutter-symbolic.svg blit.vert blit.frag + solid.vert + solid.frag debayer.vert debayer.frag diff --git a/data/solid.frag b/data/solid.frag new file mode 100644 index 0000000..bb167f7 --- /dev/null +++ b/data/solid.frag @@ -0,0 +1,9 @@ +#ifdef GL_ES +precision mediump float; +#endif + +uniform vec4 color; + +void main() { + gl_FragColor = color; +} diff --git a/data/solid.vert b/data/solid.vert new file mode 100644 index 0000000..f578403 --- /dev/null +++ b/data/solid.vert @@ -0,0 +1,9 @@ +#ifdef GL_ES +precision mediump float; +#endif + +attribute vec2 vert; + +void main() { + gl_Position = vec4(vert, 0, 1); +} diff --git a/src/main.c b/src/main.c index 76aff6a..aeac5a0 100644 --- a/src/main.c +++ b/src/main.c @@ -54,7 +54,6 @@ static MPProcessPipelineBuffer *current_preview_buffer = NULL; static int preview_buffer_width = -1; static int preview_buffer_height = -1; -static cairo_surface_t *status_surface = NULL; static char last_path[260] = ""; static MPZBarScanResult *zbar_result = NULL; @@ -67,10 +66,7 @@ GtkWidget *main_stack; GtkWidget *open_last_stack; GtkWidget *thumb_last; GtkWidget *process_spinner; -GtkWidget *control_box; -GtkWidget *control_name; -GtkAdjustment *control_slider; -GtkWidget *control_auto; +GtkWidget *scanned_codes; int remap(int value, int input_min, int input_max, int output_min, int output_max) @@ -241,95 +237,10 @@ mp_main_capture_completed(cairo_surface_t *thumb, const char *fname) (GSourceFunc)capture_completed, args, free); } -static void -draw_controls() -{ - // cairo_t *cr; - char iso[6]; - int temp; - char shutterangle[6]; - - if (exposure_is_manual) { - temp = (int)((float)exposure / (float)camera->capture_mode.height * - 360); - sprintf(shutterangle, "%d\u00b0", temp); - } else { - sprintf(shutterangle, "auto"); - } - - if (gain_is_manual) { - temp = remap(gain - 1, 0, gain_max, camera->iso_min, - camera->iso_max); - sprintf(iso, "%d", temp); - } else { - sprintf(iso, "auto"); - } - - if (status_surface) - cairo_surface_destroy(status_surface); - - // Make a service to show status of controls, 32px high - // 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 GLuint solid_program; +static GLuint solid_uniform_color; static GLuint quad; static void @@ -361,9 +272,32 @@ preview_realize(GtkGLArea *area) blit_uniform_texture = glGetUniformLocation(blit_program, "texture"); + GLuint solid_shaders[] = { + gl_util_load_shader("/org/postmarketos/Megapixels/solid.vert", GL_VERTEX_SHADER, NULL, 0), + gl_util_load_shader("/org/postmarketos/Megapixels/solid.frag", GL_FRAGMENT_SHADER, NULL, 0), + }; + + solid_program = gl_util_link_program(solid_shaders, 2); + glBindAttribLocation(solid_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert"); + check_gl(); + + solid_uniform_color = glGetUniformLocation(solid_program, "color"); + quad = gl_util_new_quad(); } +static void +position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y) +{ + double ratio = preview_buffer_height / (double)preview_buffer_width; + + *offset_x = 0; + *offset_y = 0; + *size_x = preview_width; + *size_y = preview_width * ratio; + +} + static gboolean preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) { @@ -382,11 +316,12 @@ preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); - double ratio = preview_buffer_height / (double)preview_buffer_width; - glViewport(0, - preview_height - preview_width * ratio, - preview_width, - preview_width * ratio); + float offset_x, offset_y, size_x, size_y; + position_preview(&offset_x, &offset_y, &size_x, &size_y); + glViewport(offset_x, + preview_height - size_y - offset_y, + size_x, + size_y); if (current_preview_buffer) { glUseProgram(blit_program); @@ -400,50 +335,56 @@ preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) gl_util_draw_quad(quad); } - /* - // Clear preview area with black - cairo_paint(cr); - - if (surface) { - // Draw camera preview - cairo_save(cr); - - int width = cairo_image_surface_get_width(surface); - int height = cairo_image_surface_get_height(surface); - transform_centered(cr, preview_width, preview_height, width, height); - - cairo_set_source_surface(cr, surface, 0, 0); - cairo_paint(cr); - - // Draw zbar image - if (zbar_result) { - for (uint8_t i = 0; i < zbar_result->size; ++i) { - MPZBarCode *code = &zbar_result->codes[i]; - - cairo_set_line_width(cr, 3.0); - cairo_set_source_rgba(cr, 0, 0.5, 1, 0.75); - cairo_new_path(cr); - cairo_move_to(cr, code->bounds_x[0], code->bounds_y[0]); - for (uint8_t i = 0; i < 4; ++i) { - cairo_line_to(cr, code->bounds_x[i], code->bounds_y[i]); - } - cairo_close_path(cr); - cairo_stroke(cr); - - cairo_save(cr); - cairo_translate(cr, code->bounds_x[0], code->bounds_y[0]); - cairo_show_text(cr, code->data); - cairo_restore(cr); - } + if (zbar_result) { + GLuint buffer; + if (!gtk_gl_area_get_use_es(area)) { + glGenBuffers(1, &buffer); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + check_gl(); } - cairo_restore(cr); - } + glUseProgram(solid_program); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - // Draw control overlay - cairo_set_source_surface(cr, status_surface, 0, 0); - cairo_paint(cr); - */ + glUniform4f(solid_uniform_color, 1, 0, 0, 0.5); + + for (uint8_t i = 0; i < zbar_result->size; ++i) { + MPZBarCode *code = &zbar_result->codes[i]; + + GLfloat vertices[] = { + code->bounds_x[0], code->bounds_y[0], + code->bounds_x[1], code->bounds_y[1], + code->bounds_x[3], code->bounds_y[3], + code->bounds_x[2], code->bounds_y[2], + }; + + for (int i = 0; i < 4; ++i) { + vertices[i * 2] = 2 * vertices[i * 2] / preview_buffer_width - 1.0; + vertices[i * 2 + 1] = 1.0 - 2 * vertices[i * 2 + 1] / preview_buffer_height; + } + + if (gtk_gl_area_get_use_es(area)) { + glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, vertices); + check_gl(); + glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE); + check_gl(); + } else { + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW); + check_gl(); + + glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE); + check_gl(); + } + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + check_gl(); + } + + glDisable(GL_BLEND); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } glFlush(); @@ -463,8 +404,6 @@ preview_resize(GtkWidget *widget, int width, int height, gpointer data) update_io_pipeline(); } - draw_controls(); - return TRUE; } @@ -508,110 +447,124 @@ run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app) g_application_quit(app); } -// 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; +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; -// } + 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; -// } + 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); +static void +on_zbar_dialog_response(GtkDialog *dialog, int response, char *data) +{ + GError *error = NULL; + switch (response) { + 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: + { + GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(dialog)); + gdk_clipboard_set_text( + gdk_display_get_primary_clipboard(display), + data); + } + case GTK_RESPONSE_CANCEL: + break; + default: + g_printerr("Wrong dialog response: %d\n", response); + } -// char* data = strdup(code->data); + g_free(data); + gtk_window_destroy(GTK_WINDOW(dialog)); +} -// 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); +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); -// int result = gtk_dialog_run(GTK_DIALOG(dialog)); + char* data = strdup(code->data); -// 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); -// } + if (data_is_url) { + dialog = gtk_message_dialog_new( + GTK_WINDOW(gtk_widget_get_root(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_root(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); -void + g_signal_connect(dialog, "response", G_CALLBACK(on_zbar_dialog_response), data); + + gtk_widget_show(GTK_WIDGET(dialog)); +} + +static void preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y) { + GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)); + // 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; + if (zbar_result) { + // Transform the event coordinates to the image + float offset_x, offset_y, size_x, size_y; + position_preview(&offset_x, &offset_y, &size_x, &size_y); - // for (uint8_t i = 0; i < zbar_result->size; ++i) { - // MPZBarCode *code = &zbar_result->codes[i]; + double scale = preview_buffer_height / size_y; + int zbar_x = (x - offset_x) * scale; + int zbar_y = (y - offset_y) * scale; - // if (check_point_inside_bounds(x, y, code->bounds_x, code->bounds_y)) { - // on_zbar_code_tapped(widget, code); - // return; - // } - // } - // } + for (uint8_t i = 0; i < zbar_result->size; ++i) { + MPZBarCode *code = &zbar_result->codes[i]; + + if (check_point_inside_bounds(zbar_x, zbar_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) { @@ -886,6 +839,7 @@ activate(GtkApplication *app, gpointer data) open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack")); thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last")); process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner")); + scanned_codes = GTK_WIDGET(gtk_builder_get_object(builder, "scanned-codes")); g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL); diff --git a/src/process_pipeline.c b/src/process_pipeline.c index b9aba03..18ffb80 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -608,6 +608,9 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) memcpy(image, buffer->data, size); mp_io_pipeline_release_buffer(buffer->index); + MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera->rotate, camera->mirrored); + mp_zbar_pipeline_process_image(mp_zbar_image_ref(zbar_image)); + cairo_surface_t *thumb = process_image_for_preview(image); if (captures_remaining > 0) { @@ -626,7 +629,7 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) assert(!thumb); } - free(image); + mp_zbar_image_unref(zbar_image); ++frames_processed; if (captures_remaining == 0) { diff --git a/src/zbar_pipeline.c b/src/zbar_pipeline.c index e4c9257..ce86161 100644 --- a/src/zbar_pipeline.c +++ b/src/zbar_pipeline.c @@ -6,6 +6,17 @@ #include #include +struct _MPZBarImage { + uint8_t *data; + MPPixelFormat pixel_format; + int width; + int height; + int rotation; + bool mirrored; + + _Atomic int ref_count; +}; + static MPPipeline *pipeline; static volatile int frames_processed = 0; @@ -33,7 +44,8 @@ mp_zbar_pipeline_stop() mp_pipeline_free(pipeline); } -static bool is_3d_code(zbar_symbol_type_t type) +static bool +is_3d_code(zbar_symbol_type_t type) { switch (type) { case ZBAR_EAN2: @@ -62,9 +74,41 @@ static bool is_3d_code(zbar_symbol_type_t type) } } -static MPZBarCode -process_symbol(const zbar_symbol_t *symbol) +static inline void +map_coords(int *x, int *y, int width, int height, int rotation, bool mirrored) { + int x_r, y_r; + if (rotation == 0) { + x_r = *x; + y_r = *y; + } else if (rotation == 90) { + x_r = *y; + y_r = height - *x - 1; + } else if (rotation == 270) { + x_r = width - *y - 1; + y_r = *x; + } else { + x_r = width - *x - 1; + y_r = height - *y - 1; + } + + if (mirrored) { + x_r = width - x_r - 1; + } + + *x = x_r; + *y = y_r; +} + +static MPZBarCode +process_symbol(const MPZBarImage *image, int width, int height, const zbar_symbol_t *symbol) +{ + if (image->rotation == 90 || image->rotation == 270) { + int tmp = width; + width = height; + height = tmp; + } + MPZBarCode code; unsigned loc_size = zbar_symbol_get_loc_size(symbol); @@ -100,6 +144,10 @@ process_symbol(const zbar_symbol_t *symbol) code.bounds_y[3] = max_y; } + for (uint8_t i = 0; i < 4; ++i) { + map_coords(&code.bounds_x[i], &code.bounds_y[i], width, height, image->rotation, image->mirrored); + } + const char *data = zbar_symbol_get_data(symbol); unsigned int data_size = zbar_symbol_get_data_length(symbol); code.type = zbar_get_symbol_name(type); @@ -110,18 +158,26 @@ process_symbol(const zbar_symbol_t *symbol) } static void -process_surface(MPPipeline *pipeline, cairo_surface_t **_surface) +process_image(MPPipeline *pipeline, MPZBarImage **_image) { - cairo_surface_t *surface = *_surface; + MPZBarImage *image = *_image; - int width = cairo_image_surface_get_width(surface); - int height = cairo_image_surface_get_height(surface); - const uint32_t *surface_data = (const uint32_t *)cairo_image_surface_get_data(surface); + assert(image->pixel_format == MP_PIXEL_FMT_BGGR8 + || image->pixel_format == MP_PIXEL_FMT_GBRG8 + || image->pixel_format == MP_PIXEL_FMT_GRBG8 + || image->pixel_format == MP_PIXEL_FMT_RGGB8); + + // Create a grayscale image for scanning from the current preview. + // Rotate/mirror correctly. + int width = image->width / 2; + int height = image->height / 2; - // Create a grayscale image for scanning from the current preview uint8_t *data = malloc(width * height * sizeof(uint8_t)); - for (size_t i = 0; i < width * height; ++i) { - data[i] = (surface_data[i] >> 16) & 0xff; + size_t i = 0; + for (int y = 0; y < image->height; y += 2) { + for (int x = 0; x < image->width; x += 2) { + data[++i] = image->data[x + image->width * y]; + } } // Create image for zbar @@ -140,7 +196,7 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface) const zbar_symbol_t *symbol = zbar_image_first_symbol(zbar_image); for (int i = 0; i < MIN(res, 8); ++i) { assert(symbol != NULL); - result->codes[i] = process_symbol(symbol); + result->codes[i] = process_symbol(image, width, height, symbol); symbol = zbar_symbol_next(symbol); } @@ -150,22 +206,57 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface) } zbar_image_destroy(zbar_image); - cairo_surface_destroy(surface); + mp_zbar_image_unref(image); ++frames_processed; } void -mp_zbar_pipeline_process_image(cairo_surface_t *surface) +mp_zbar_pipeline_process_image(MPZBarImage *image) { // If we haven't processed the previous frame yet, drop this one if (frames_received != frames_processed) { - cairo_surface_destroy(surface); + mp_zbar_image_unref(image); return; } ++frames_received; - mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_surface, &surface, - sizeof(cairo_surface_t *)); + mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image, + sizeof(MPZBarImage *)); +} + +MPZBarImage * +mp_zbar_image_new(uint8_t *data, + MPPixelFormat pixel_format, + int width, + int height, + int rotation, + bool mirrored) +{ + MPZBarImage *image = malloc(sizeof(MPZBarImage)); + image->data = data; + image->pixel_format = pixel_format; + image->width = width; + image->height = height; + image->rotation = rotation; + image->mirrored = mirrored; + image->ref_count = 1; + return image; +} + +MPZBarImage * +mp_zbar_image_ref(MPZBarImage *image) +{ + ++image->ref_count; + return image; +} + +void +mp_zbar_image_unref(MPZBarImage *image) +{ + if (--image->ref_count == 0) { + free(image->data); + free(image); + } } diff --git a/src/zbar_pipeline.h b/src/zbar_pipeline.h index 150861a..410e906 100644 --- a/src/zbar_pipeline.h +++ b/src/zbar_pipeline.h @@ -2,7 +2,7 @@ #include "camera_config.h" -typedef struct _cairo_surface cairo_surface_t; +typedef struct _MPZBarImage MPZBarImage; typedef struct { int bounds_x[4]; @@ -19,4 +19,13 @@ typedef struct { void mp_zbar_pipeline_start(); void mp_zbar_pipeline_stop(); -void mp_zbar_pipeline_process_image(cairo_surface_t *surface); +void mp_zbar_pipeline_process_image(MPZBarImage *image); + +MPZBarImage *mp_zbar_image_new(uint8_t *data, + MPPixelFormat pixel_format, + int width, + int height, + int rotation, + bool mirrored); +MPZBarImage *mp_zbar_image_ref(MPZBarImage *image); +void mp_zbar_image_unref(MPZBarImage *image); From 1bd975698c8ab53faaab2d9d869148c04aaae5fd Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Mon, 26 Apr 2021 19:07:33 +1000 Subject: [PATCH 10/21] Average green samples for debayering Gives a slightly cleaner image --- data/debayer.frag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/debayer.frag b/data/debayer.frag index 8e9f184..9a98c10 100644 --- a/data/debayer.frag +++ b/data/debayer.frag @@ -22,7 +22,7 @@ void main() { // Assume BGGR for now. Currently this just takes 3 of the four samples // for each pixel, there's room here to do some better debayering. - vec3 color = samples.wyx; + vec3 color = vec3(samples.w, (samples.y + samples.w) / 2.0, samples.x); // Fast SRGB estimate. See https://mimosa-pudica.net/fast-gamma/ vec3 srgb_color = (vec3(1.138) * inversesqrt(color) - vec3(0.138)) * color; @@ -30,5 +30,5 @@ void main() { // Slow SRGB estimate // vec3 srgb_color = pow(color, vec3(1.0 / 2.2)); - gl_FragColor = vec4(color_matrix * srgb_color, 0); + gl_FragColor = vec4(color_matrix * srgb_color, 1); } From d03d4b99e311f266f37a28aff88bb180e8b1353e Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Mon, 26 Apr 2021 19:07:59 +1000 Subject: [PATCH 11/21] Make pressing the preview work again --- data/camera.css | 3 +- data/camera.ui | 157 ++++++++++++++++++++++------------------- src/main.c | 6 +- src/process_pipeline.c | 4 ++ 4 files changed, 93 insertions(+), 77 deletions(-) diff --git a/data/camera.css b/data/camera.css index 316209d..85c65e3 100644 --- a/data/camera.css +++ b/data/camera.css @@ -4,9 +4,10 @@ } .controlbox { - background: rgba(0,0,0,0.2); + background: rgba(0, 0, 0, 0.2); } .button-overlay { opacity: 0.9; + background-color: rgba(0, 0, 0, 0.2); } diff --git a/data/camera.ui b/data/camera.ui index 9a3cc76..a6580d1 100644 --- a/data/camera.ui +++ b/data/camera.ui @@ -25,19 +25,19 @@ vertical - 1 - 1 + fill + start 5 0 - 5 - 5 - 5 - 5 horizontal 1 start + 5 + 5 + 5 + 5 0 5 @@ -64,6 +64,10 @@ 0 start + 5 + 5 + 5 + 5 horizontal @@ -85,87 +89,94 @@ + + + + + + horizontal + fill + end horizontal 1 - 1 end - 0 + 5 + 5 + 5 + 5 - - horizontal - 1 - end + + app.open-settings + settings-symbolic + + + + + app.switch-camera + switch-camera-symbolic + + + + + + + 5 + 5 + app.capture + + + 60 0 - 5 + shutter-button-symbolic + + + + + + + + horizontal + 1 + end + end + 5 + 5 + 5 + 5 + + + app.open-last - - app.open-settings - settings-symbolic - - - - - app.switch-camera - switch-camera-symbolic + + 0 + + + 24 + 24 + 0 + + + + + 24 + 24 + 0 + + - app.capture - - - 60 - 0 - shutter-button-symbolic - - - - - - - - horizontal - 1 - end - end - 0 - 5 - - - app.open-last - - - 0 - - - 24 - 24 - 0 - - - - - 24 - 24 - 0 - - - - - - - - - app.open-photos - folder-symbolic - - + app.open-photos + folder-symbolic diff --git a/src/main.c b/src/main.c index aeac5a0..804e718 100644 --- a/src/main.c +++ b/src/main.c @@ -545,6 +545,7 @@ static void preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y) { GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)); + int scale_factor = gtk_widget_get_scale_factor(widget); // Tapped zbar result if (zbar_result) { @@ -552,9 +553,8 @@ preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y) float offset_x, offset_y, size_x, size_y; position_preview(&offset_x, &offset_y, &size_x, &size_y); - double scale = preview_buffer_height / size_y; - int zbar_x = (x - offset_x) * scale; - int zbar_y = (y - offset_y) * scale; + int zbar_x = (x - offset_x) * scale_factor / size_x * preview_buffer_width; + int zbar_y = (y - offset_y) * scale_factor / size_y * preview_buffer_height; for (uint8_t i = 0; i < zbar_result->size; ++i) { MPZBarCode *code = &zbar_result->codes[i]; diff --git a/src/process_pipeline.c b/src/process_pipeline.c index 18ffb80..ae8cebe 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -255,7 +255,9 @@ mp_process_pipeline_init_gl(GdkSurface *surface) static cairo_surface_t * process_image_for_preview(const uint8_t *image) { +#ifdef PROFILE_DEBAYER clock_t t1 = clock(); +#endif // Pick an available buffer MPProcessPipelineBuffer *output_buffer = NULL; @@ -293,8 +295,10 @@ process_image_for_preview(const uint8_t *image) glDeleteTextures(1, &input_texture); +#ifdef PROFILE_DEBAYER clock_t t2 = clock(); printf("%fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000); +#endif // { // glBindTexture(GL_TEXTURE_2D, textures[1]); From 36b7b2b38de82c8b018e7d1992045dbb0115336f Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Mon, 26 Apr 2021 22:21:15 +1000 Subject: [PATCH 12/21] Fix gain and exposure being switched in io pipeline --- src/io_pipeline.c | 10 +++--- src/main.c | 80 ----------------------------------------------- 2 files changed, 5 insertions(+), 85 deletions(-) diff --git a/src/io_pipeline.c b/src/io_pipeline.c index 1ebb93d..22c882d 100644 --- a/src/io_pipeline.c +++ b/src/io_pipeline.c @@ -497,15 +497,15 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state) pipeline, info->camera, on_frame, NULL); current_controls.gain_is_manual = - mp_camera_control_get_int32( - info->camera, V4L2_CID_EXPOSURE_AUTO) == - V4L2_EXPOSURE_MANUAL; + mp_camera_control_get_bool(info->camera, + V4L2_CID_AUTOGAIN) == 0; current_controls.gain = mp_camera_control_get_int32( info->camera, info->gain_ctrl); current_controls.exposure_is_manual = - mp_camera_control_get_bool(info->camera, - V4L2_CID_AUTOGAIN) == 0; + mp_camera_control_get_int32( + info->camera, V4L2_CID_EXPOSURE_AUTO) == + V4L2_EXPOSURE_MANUAL; current_controls.exposure = mp_camera_control_get_int32( info->camera, V4L2_CID_EXPOSURE); } diff --git a/src/main.c b/src/main.c index 804e718..1211f17 100644 --- a/src/main.c +++ b/src/main.c @@ -712,86 +712,6 @@ open_shutter_controls(GtkWidget *button, gpointer user_data) open_controls(button, "Shutter", 1.0, 360.0, exposure, !exposure_is_manual, set_shutter, set_shutter_auto); } -/* -static void -on_control_auto_toggled(GtkToggleButton *widget, gpointer user_data) -{ - bool is_manual = gtk_toggle_button_get_active(widget) ? false : true; - bool has_changed; - - switch (current_control) { - case USER_CONTROL_ISO: - if (gain_is_manual != is_manual) { - gain_is_manual = is_manual; - has_changed = true; - } - break; - case USER_CONTROL_SHUTTER: - if (exposure_is_manual != is_manual) { - exposure_is_manual = is_manual; - has_changed = true; - } - break; - } - - if (has_changed) { - // The slider might have been moved while Auto mode is active. When entering - // Manual mode, first read the slider value to sync with those changes. - double value = gtk_adjustment_get_value(control_slider); - switch (current_control) { - case USER_CONTROL_ISO: - if (value != gain) { - gain = (int)value; - } - break; - case USER_CONTROL_SHUTTER: { - // So far all sensors use exposure time in number of sensor rows - int new_exposure = - (int)(value / 360.0 * camera->capture_mode.height); - if (new_exposure != exposure) { - exposure = new_exposure; - } - break; - } - } - - update_io_pipeline(); - draw_controls(); - } -} - -static void -on_control_slider_changed(GtkAdjustment *widget, gpointer user_data) -{ - double value = gtk_adjustment_get_value(widget); - - bool has_changed = false; - switch (current_control) { - case USER_CONTROL_ISO: - if (value != gain) { - gain = (int)value; - has_changed = true; - } - break; - case USER_CONTROL_SHUTTER: { - // So far all sensors use exposure time in number of sensor rows - int new_exposure = - (int)(value / 360.0 * camera->capture_mode.height); - if (new_exposure != exposure) { - exposure = new_exposure; - has_changed = true; - } - break; - } - } - - if (has_changed) { - update_io_pipeline(); - draw_controls(); - } -} -*/ - static void on_realize(GtkWidget *window, gpointer *data) { From 207aabf16a6daf423e3635397e0d3619d6f16835 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Sat, 1 May 2021 01:31:37 +1000 Subject: [PATCH 13/21] Add workaround for OV5640 capture mode not being set on first start after reboot --- config/pine64,pinephone-1.0.ini | 2 +- config/pine64,pinephone-1.1.ini | 2 +- config/pine64,pinephone-1.2.ini | 2 +- src/io_pipeline.c | 6 ++++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/config/pine64,pinephone-1.0.ini b/config/pine64,pinephone-1.0.ini index b97fcc4..901ccc7 100644 --- a/config/pine64,pinephone-1.0.ini +++ b/config/pine64,pinephone-1.0.ini @@ -11,7 +11,7 @@ capture-rate=10 capture-fmt=BGGR8 preview-width=1280 preview-height=720 -preview-rate=60 +preview-rate=30 preview-fmt=BGGR8 rotate=270 colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050 diff --git a/config/pine64,pinephone-1.1.ini b/config/pine64,pinephone-1.1.ini index b97fcc4..901ccc7 100644 --- a/config/pine64,pinephone-1.1.ini +++ b/config/pine64,pinephone-1.1.ini @@ -11,7 +11,7 @@ capture-rate=10 capture-fmt=BGGR8 preview-width=1280 preview-height=720 -preview-rate=60 +preview-rate=30 preview-fmt=BGGR8 rotate=270 colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050 diff --git a/config/pine64,pinephone-1.2.ini b/config/pine64,pinephone-1.2.ini index bab05aa..e295f87 100644 --- a/config/pine64,pinephone-1.2.ini +++ b/config/pine64,pinephone-1.2.ini @@ -11,7 +11,7 @@ capture-rate=10 capture-fmt=BGGR8 preview-width=1280 preview-height=720 -preview-rate=60 +preview-rate=30 preview-fmt=BGGR8 rotate=270 mirrored=false diff --git a/src/io_pipeline.c b/src/io_pipeline.c index 22c882d..db50cfe 100644 --- a/src/io_pipeline.c +++ b/src/io_pipeline.c @@ -185,6 +185,12 @@ setup_camera(MPDeviceList **device_list, const struct mp_camera_config *config) info->camera = mp_camera_new(dev_info->video_fd, info->fd); + // Start with the capture format, this works around a bug with + // the ov5640 driver where it won't allow setting the preview + // format initially. + MPCameraMode mode = config->capture_mode; + mp_camera_set_mode(info->camera, &mode); + // Trigger continuous auto focus if the sensor supports it if (mp_camera_query_control(info->camera, V4L2_CID_FOCUS_AUTO, NULL)) { From 70f8ddbada5801438ad9ed7864e3a8a5d2e0c0f0 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Sat, 1 May 2021 20:12:58 +1000 Subject: [PATCH 14/21] Improve preview positioning --- data/camera.ui | 4 ++-- src/main.c | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/data/camera.ui b/data/camera.ui index a6580d1..c3c8732 100644 --- a/data/camera.ui +++ b/data/camera.ui @@ -23,7 +23,7 @@ - + vertical fill start @@ -95,7 +95,7 @@ - + horizontal fill end diff --git a/src/main.c b/src/main.c index 1211f17..458c32b 100644 --- a/src/main.c +++ b/src/main.c @@ -67,6 +67,8 @@ GtkWidget *open_last_stack; GtkWidget *thumb_last; GtkWidget *process_spinner; GtkWidget *scanned_codes; +GtkWidget *preview_top_box; +GtkWidget *preview_bottom_box; int remap(int value, int input_min, int input_max, int output_min, int output_max) @@ -289,13 +291,24 @@ preview_realize(GtkGLArea *area) static void position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y) { + int scale = gtk_widget_get_scale_factor(preview); + int preview_height = gtk_widget_get_allocated_height(preview) * scale; + int top_height = gtk_widget_get_allocated_height(preview_top_box) * scale; + int bottom_height = gtk_widget_get_allocated_height(preview_bottom_box) * scale; + int inner_height = preview_height - top_height - bottom_height; + double ratio = preview_buffer_height / (double)preview_buffer_width; *offset_x = 0; - *offset_y = 0; *size_x = preview_width; *size_y = preview_width * ratio; + if (*size_y > inner_height) { + *offset_y = (preview_height - *size_y) / 2.0; + } else { + *offset_y = top_height + (inner_height - *size_y) / 2.0; + } + } static gboolean @@ -760,6 +773,8 @@ activate(GtkApplication *app, gpointer data) thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last")); process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner")); scanned_codes = GTK_WIDGET(gtk_builder_get_object(builder, "scanned-codes")); + preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box")); + preview_bottom_box = GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box")); g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL); @@ -775,7 +790,7 @@ activate(GtkApplication *app, gpointer data) // Setup actions create_simple_action(app, "capture", G_CALLBACK(run_capture_action)); - create_simple_action(app, "camera-switch", G_CALLBACK(run_camera_switch_action)); + create_simple_action(app, "switch-camera", G_CALLBACK(run_camera_switch_action)); create_simple_action(app, "open-settings", G_CALLBACK(run_open_settings_action)); create_simple_action(app, "close-settings", G_CALLBACK(run_close_settings_action)); create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action)); From 8ccf1451ed72a2839d8d0dfe39943b82d7b93920 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Sat, 1 May 2021 21:28:02 +1000 Subject: [PATCH 15/21] Fix image capture --- src/io_pipeline.c | 2 + src/main.c | 35 ++------------ src/main.h | 7 +-- src/process_pipeline.c | 101 +++++++++++++---------------------------- 4 files changed, 40 insertions(+), 105 deletions(-) diff --git a/src/io_pipeline.c b/src/io_pipeline.c index db50cfe..08edd7e 100644 --- a/src/io_pipeline.c +++ b/src/io_pipeline.c @@ -311,6 +311,7 @@ capture(MPPipeline *pipeline, const void *data) V4L2_EXPOSURE_MANUAL); // Change camera mode for capturing + mp_process_pipeline_sync(); mp_camera_stop_capture(info->camera); mode = camera->capture_mode; @@ -448,6 +449,7 @@ on_frame(MPBuffer buffer, void * _data) } // Go back to preview mode + mp_process_pipeline_sync(); mp_camera_stop_capture(info->camera); mode = camera->preview_mode; diff --git a/src/main.c b/src/main.c index 458c32b..ae486f9 100644 --- a/src/main.c +++ b/src/main.c @@ -182,34 +182,8 @@ mp_main_set_preview(MPProcessPipelineBuffer *buffer) (GSourceFunc)set_preview, buffer, NULL); } -static void transform_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height, - int src_width, int src_height) -{ - cairo_translate(cr, dst_width / 2, dst_height / 2); - - double scale = MIN(dst_width / (double)src_width, dst_height / (double)src_height); - cairo_scale(cr, scale, scale); - - cairo_translate(cr, -src_width / 2, -src_height / 2); -} - -void -draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height, - cairo_surface_t *surface) -{ - cairo_save(cr); - - int width = cairo_image_surface_get_width(surface); - int height = cairo_image_surface_get_height(surface); - transform_centered(cr, dst_width, dst_height, width, height); - - cairo_set_source_surface(cr, surface, 0, 0); - cairo_paint(cr); - cairo_restore(cr); -} - struct capture_completed_args { - cairo_surface_t *thumb; + GdkTexture *thumb; char *fname; }; @@ -218,19 +192,20 @@ 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_paintable(GTK_IMAGE(thumb_last), + GDK_PAINTABLE(args->thumb)); gtk_spinner_stop(GTK_SPINNER(process_spinner)); gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last); - cairo_surface_destroy(args->thumb); + g_object_unref(args->thumb); g_free(args->fname); return false; } void -mp_main_capture_completed(cairo_surface_t *thumb, const char *fname) +mp_main_capture_completed(GdkTexture *thumb, const char *fname) { struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args)); args->thumb = thumb; diff --git a/src/main.h b/src/main.h index 720b4c5..bbebca9 100644 --- a/src/main.h +++ b/src/main.h @@ -5,8 +5,6 @@ #include "process_pipeline.h" #include "gtk/gtk.h" -#define MP_MAIN_THUMB_SIZE 24 - struct mp_main_state { const struct mp_camera_config *camera; MPCameraMode mode; @@ -28,11 +26,8 @@ struct mp_main_state { void mp_main_update_state(const struct mp_main_state *state); void mp_main_set_preview(MPProcessPipelineBuffer *buffer); -void mp_main_capture_completed(cairo_surface_t *thumb, const char *fname); +void mp_main_capture_completed(GdkTexture *thumb, const char *fname); void mp_main_set_zbar_result(MPZBarScanResult *result); int remap(int value, int input_min, int input_max, int output_min, int output_max); - -void draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height, - cairo_surface_t *surface); diff --git a/src/process_pipeline.c b/src/process_pipeline.c index ae8cebe..f0da6ed 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -40,6 +40,9 @@ static int captures_remaining = 0; static int preview_width; static int preview_height; +static int output_buffer_width = -1; +static int output_buffer_height = -1; + // static bool gain_is_manual; static int gain; static int gain_max; @@ -252,7 +255,7 @@ mp_process_pipeline_init_gl(GdkSurface *surface) mp_pipeline_invoke(pipeline, (MPPipelineCallback) init_gl, &surface, sizeof(GdkSurface *)); } -static cairo_surface_t * +static GdkTexture * process_image_for_preview(const uint8_t *image) { #ifdef PROFILE_DEBAYER @@ -300,55 +303,6 @@ process_image_for_preview(const uint8_t *image) printf("%fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000); #endif - // { - // 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); #endif @@ -357,20 +311,33 @@ process_image_for_preview(const uint8_t *image) 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); + GdkTexture *thumb = NULL; + if (captures_remaining == 1) { + printf("Making thumbnail\n"); - // cairo_t *cr = cairo_create(thumb); - // draw_surface_scaled_centered( - // cr, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE, surface); - // cairo_destroy(cr); - // } + size_t size = output_buffer_width * output_buffer_height * sizeof(uint32_t); - // Pass processed preview to main and zbar - // mp_zbar_pipeline_process_image(cairo_surface_reference(surface)); + uint32_t *data = g_malloc_n(size, 1); + + glReadPixels(0, 0, output_buffer_width, output_buffer_height, GL_RGBA, GL_UNSIGNED_BYTE, data); + check_gl(); + + // Flip vertically + for (size_t y = 0; y < output_buffer_height / 2; ++y) { + for (size_t x = 0; x < output_buffer_width; ++x) { + uint32_t tmp = data[(output_buffer_height - y - 1) * output_buffer_width + x]; + data[(output_buffer_height - y - 1) * output_buffer_width + x] = data[y * output_buffer_width + x]; + data[y * output_buffer_width + x] = tmp; + } + } + + thumb = gdk_memory_texture_new( + output_buffer_width, + output_buffer_height, + GDK_MEMORY_R8G8B8A8, + g_bytes_new_take(data, size), + output_buffer_width * sizeof(uint32_t)); + } return thumb; } @@ -527,7 +494,7 @@ process_image_for_capture(const uint8_t *image, int count) } static void -post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thumb) +post_process_finished(GSubprocess *proc, GAsyncResult *res, GdkTexture *thumb) { char *stdout; g_subprocess_communicate_utf8_finish(proc, res, &stdout, NULL, NULL); @@ -550,7 +517,7 @@ post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thu } static void -process_capture_burst(cairo_surface_t *thumb) +process_capture_burst(GdkTexture *thumb) { time_t rawtime; time(&rawtime); @@ -615,7 +582,7 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera->rotate, camera->mirrored); mp_zbar_pipeline_process_image(mp_zbar_image_ref(zbar_image)); - cairo_surface_t *thumb = process_image_for_preview(image); + GdkTexture *thumb = process_image_for_preview(image); if (captures_remaining > 0) { int count = burst_length - captures_remaining; @@ -646,7 +613,6 @@ mp_process_pipeline_process_image(MPBuffer buffer) { // If we haven't processed the previous frame yet, drop this one if (frames_received != frames_processed && !is_capturing) { - printf("Dropped frame at capture\n"); mp_io_pipeline_release_buffer(buffer.index); return; } @@ -682,9 +648,6 @@ mp_process_pipeline_capture() mp_pipeline_invoke(pipeline, capture, NULL, 0); } -static int output_buffer_width = -1; -static int output_buffer_height = -1; - static void on_output_changed() { From 46bfb39dfd7da9584748a500def059997fd51866 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Sat, 1 May 2021 23:36:12 +1000 Subject: [PATCH 16/21] Cleanup --- src/process_pipeline.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/process_pipeline.c b/src/process_pipeline.c index f0da6ed..3690a6b 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -300,7 +300,7 @@ process_image_for_preview(const uint8_t *image) #ifdef PROFILE_DEBAYER clock_t t2 = clock(); - printf("%fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000); + printf("process_image_for_preview %fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000); #endif #ifdef RENDERDOC @@ -572,6 +572,10 @@ process_capture_burst(GdkTexture *thumb) static void process_image(MPPipeline *pipeline, const MPBuffer *buffer) { +#ifdef PROFILE_PROCESS + clock_t t1 = clock(); +#endif + size_t size = mp_pixel_format_width_to_bytes(mode.pixel_format, mode.width) * mode.height; @@ -582,6 +586,10 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera->rotate, camera->mirrored); mp_zbar_pipeline_process_image(mp_zbar_image_ref(zbar_image)); +#ifdef PROFILE_PROCESS + clock_t t2 = clock(); +#endif + GdkTexture *thumb = process_image_for_preview(image); if (captures_remaining > 0) { @@ -606,6 +614,14 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) if (captures_remaining == 0) { is_capturing = false; } + +#ifdef PROFILE_PROCESS + clock_t t3 = clock(); + printf("process_image %fms, step 1:%fms, step 2:%fms\n", + (float)(t3 - t1) / CLOCKS_PER_SEC * 1000, + (float)(t2 - t1) / CLOCKS_PER_SEC * 1000, + (float)(t3 - t2) / CLOCKS_PER_SEC * 1000); +#endif } void @@ -723,5 +739,5 @@ mp_process_pipeline_update_state(const struct mp_process_pipeline_state *new_sta sizeof(struct mp_process_pipeline_state)); } -// FUCK YOU GTK +// GTK4 seems to require this void pango_fc_font_get_languages() {} From 64b75bcbe58a9b673a18087d93018192d1660490 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Tue, 4 May 2021 00:06:55 +1000 Subject: [PATCH 17/21] Preview rotation follows device --- data/blit.vert | 4 +- src/io_pipeline.c | 7 ++- src/io_pipeline.h | 2 + src/main.c | 114 ++++++++++++++++++++++++++++++++++++++--- src/process_pipeline.c | 28 ++++++++-- src/process_pipeline.h | 2 + 6 files changed, 142 insertions(+), 15 deletions(-) diff --git a/data/blit.vert b/data/blit.vert index 557c872..e6e75d4 100644 --- a/data/blit.vert +++ b/data/blit.vert @@ -5,10 +5,12 @@ precision mediump float; attribute vec2 vert; attribute vec2 tex_coord; +uniform mat3 transform; + varying vec2 uv; void main() { uv = tex_coord; - gl_Position = vec4(vert, 0, 1); + gl_Position = vec4(transform * vec3(vert, 1), 1); } diff --git a/src/io_pipeline.c b/src/io_pipeline.c index 08edd7e..1281df3 100644 --- a/src/io_pipeline.c +++ b/src/io_pipeline.c @@ -75,6 +75,8 @@ static int captures_remaining = 0; static int preview_width; static int preview_height; +static int device_rotation; + struct control_state { bool gain_is_manual; int gain; @@ -275,6 +277,7 @@ update_process_pipeline() .burst_length = burst_length, .preview_width = preview_width, .preview_height = preview_height, + .device_rotation = device_rotation, .gain_is_manual = current_controls.gain_is_manual, .gain = current_controls.gain, .gain_max = info->gain_max, @@ -521,11 +524,13 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state) has_changed = has_changed || burst_length != state->burst_length || preview_width != state->preview_width || - preview_height != state->preview_height; + preview_height != state->preview_height || + device_rotation != state->device_rotation; burst_length = state->burst_length; preview_width = state->preview_width; preview_height = state->preview_height; + device_rotation = state->device_rotation; if (camera) { struct control_state previous_desired = desired_controls; diff --git a/src/io_pipeline.h b/src/io_pipeline.h index 1814f84..62b0c8b 100644 --- a/src/io_pipeline.h +++ b/src/io_pipeline.h @@ -10,6 +10,8 @@ struct mp_io_pipeline_state { int preview_width; int preview_height; + int device_rotation; + bool gain_is_manual; int gain; diff --git a/src/main.c b/src/main.c index ae486f9..8458daa 100644 --- a/src/main.c +++ b/src/main.c @@ -40,6 +40,8 @@ static MPCameraMode mode; static int preview_width = -1; static int preview_height = -1; +static int device_rotation = 0; + static bool gain_is_manual = false; static int gain; static int gain_max; @@ -95,6 +97,7 @@ update_io_pipeline() .burst_length = burst_length, .preview_width = preview_width, .preview_height = preview_height, + .device_rotation = device_rotation, .gain_is_manual = gain_is_manual, .gain = gain, .exposure_is_manual = exposure_is_manual, @@ -215,6 +218,7 @@ mp_main_capture_completed(GdkTexture *thumb, const char *fname) } static GLuint blit_program; +static GLuint blit_uniform_transform; static GLuint blit_uniform_texture; static GLuint solid_program; static GLuint solid_uniform_color; @@ -247,6 +251,7 @@ preview_realize(GtkGLArea *area) glBindAttribLocation(blit_program, GL_UTIL_TEX_COORD_ATTRIBUTE, "tex_coord"); check_gl(); + blit_uniform_transform = glGetUniformLocation(blit_program, "transform"); blit_uniform_texture = glGetUniformLocation(blit_program, "texture"); GLuint solid_shaders[] = { @@ -266,17 +271,26 @@ preview_realize(GtkGLArea *area) static void position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y) { - int scale = gtk_widget_get_scale_factor(preview); - int preview_height = gtk_widget_get_allocated_height(preview) * scale; - int top_height = gtk_widget_get_allocated_height(preview_top_box) * scale; - int bottom_height = gtk_widget_get_allocated_height(preview_bottom_box) * scale; + int buffer_width, buffer_height; + if (device_rotation == 0 || device_rotation == 180) { + buffer_width = preview_buffer_width; + buffer_height = preview_buffer_height; + } else { + buffer_width = preview_buffer_height; + buffer_height = preview_buffer_width; + } + + int scale_factor = gtk_widget_get_scale_factor(preview); + int top_height = gtk_widget_get_allocated_height(preview_top_box) * scale_factor; + int bottom_height = gtk_widget_get_allocated_height(preview_bottom_box) * scale_factor; int inner_height = preview_height - top_height - bottom_height; - double ratio = preview_buffer_height / (double)preview_buffer_width; + double scale = MIN(preview_width / (float) buffer_width, preview_height / (float) buffer_height); - *offset_x = 0; - *size_x = preview_width; - *size_y = preview_width * ratio; + *size_x = scale * buffer_width; + *size_y = scale * buffer_height; + + *offset_x = (preview_width - *size_x) / 2.0; if (*size_y > inner_height) { *offset_y = (preview_height - *size_y) / 2.0; @@ -314,6 +328,19 @@ preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) if (current_preview_buffer) { glUseProgram(blit_program); + GLfloat rotation_list[4] = { 0, -1, 0, 1 }; + int rotation_index = device_rotation / 90; + + GLfloat sin_rot = rotation_list[rotation_index]; + GLfloat cos_rot = rotation_list[(4 + rotation_index - 1) % 4]; + GLfloat matrix[9] = { + cos_rot, sin_rot, 0, + -sin_rot, cos_rot, 0, + 0, 0, 1, + }; + glUniformMatrix3fv(blit_uniform_transform, 1, GL_FALSE, matrix); + check_gl(); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, mp_process_pipeline_buffer_get_texture_id(current_preview_buffer)); glUniform1i(blit_uniform_texture, 0); @@ -719,6 +746,62 @@ create_simple_action(GtkApplication *app, const char *name, GCallback callback) return action; } +static void display_config_received(GDBusConnection *conn, GAsyncResult *res, gpointer user_data) +{ + GError *error = NULL; + GVariant *result = g_dbus_connection_call_finish(conn, res, &error); + + if (!result) { + printf("Failed to get display configuration: %s\n", error->message); + return; + } + + GVariant *configs = g_variant_get_child_value(result, 1); + if (g_variant_n_children(configs) == 0) { + return; + } + + GVariant *config = g_variant_get_child_value(configs, 0); + GVariant *rot_config = g_variant_get_child_value(config, 7); + uint32_t rotation_index = g_variant_get_uint32(rot_config); + + assert(rotation_index < 4); + int new_rotation = rotation_index * 90; + + if (new_rotation != device_rotation) { + device_rotation = new_rotation; + update_io_pipeline(); + } +} + +static void update_screen_rotation(GDBusConnection *conn) +{ + g_dbus_connection_call(conn, + "org.gnome.Mutter.DisplayConfig", + "/org/gnome/Mutter/DisplayConfig", + "org.gnome.Mutter.DisplayConfig", + "GetResources", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + (GAsyncReadyCallback)display_config_received, + NULL); +} + +static void on_screen_rotate( + GDBusConnection *conn, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + update_screen_rotation(conn); +} + static void activate(GtkApplication *app, gpointer data) { @@ -779,6 +862,21 @@ activate(GtkApplication *app, gpointer data) const char *quit_accels[] = { "q", "w", NULL }; gtk_application_set_accels_for_action(app, "app.quit", quit_accels); + // Listen for phosh rotation + GDBusConnection *conn = g_application_get_dbus_connection(G_APPLICATION(app)); + g_dbus_connection_signal_subscribe( + conn, + NULL, + "org.gnome.Mutter.DisplayConfig", + "MonitorsChanged", + "/org/gnome/Mutter/DisplayConfig", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + &on_screen_rotate, + NULL, + NULL); + update_screen_rotation(conn); + mp_io_pipeline_start(); gtk_application_add_window(app, GTK_WINDOW(window)); diff --git a/src/process_pipeline.c b/src/process_pipeline.c index 3690a6b..7802ca6 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -31,6 +31,7 @@ static volatile int frames_processed = 0; static volatile int frames_received = 0; static const struct mp_camera_config *camera; +static int camera_rotation; static MPCameraMode mode; @@ -40,6 +41,8 @@ static int captures_remaining = 0; static int preview_width; static int preview_height; +static int device_rotation; + static int output_buffer_width = -1; static int output_buffer_height = -1; @@ -370,13 +373,13 @@ process_image_for_capture(const uint8_t *image, int count) TIFFSetField(tif, TIFFTAG_MAKE, mp_get_device_make()); TIFFSetField(tif, TIFFTAG_MODEL, mp_get_device_model()); uint16_t orientation; - if (camera->rotate == 0) { + if (camera_rotation == 0) { orientation = camera->mirrored ? ORIENTATION_TOPRIGHT : ORIENTATION_TOPLEFT; - } else if (camera->rotate == 90) { + } else if (camera_rotation == 90) { orientation = camera->mirrored ? ORIENTATION_RIGHTBOT : ORIENTATION_LEFTBOT; - } else if (camera->rotate == 180) { + } else if (camera_rotation == 180) { orientation = camera->mirrored ? ORIENTATION_BOTLEFT : ORIENTATION_BOTRIGHT; } else { @@ -583,7 +586,7 @@ process_image(MPPipeline *pipeline, const MPBuffer *buffer) memcpy(image, buffer->data, size); mp_io_pipeline_release_buffer(buffer->index); - MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera->rotate, camera->mirrored); + MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera_rotation, camera->mirrored); mp_zbar_pipeline_process_image(mp_zbar_image_ref(zbar_image)); #ifdef PROFILE_PROCESS @@ -692,10 +695,21 @@ on_output_changed() camera->blacklevel); } +static int +mod(int a, int b) +{ + int r = a % b; + return r < 0 ? r + b : r; +} + static void update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state) { - const bool output_changed = (!mp_camera_mode_is_equivalent(&mode, &state->mode) || preview_width != state->preview_width || preview_height != state->preview_height); + const bool output_changed = + !mp_camera_mode_is_equivalent(&mode, &state->mode) + || preview_width != state->preview_width + || preview_height != state->preview_height + || device_rotation != state->device_rotation; camera = state->camera; mode = state->mode; @@ -703,6 +717,8 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state preview_width = state->preview_width; preview_height = state->preview_height; + device_rotation = state->device_rotation; + burst_length = state->burst_length; // gain_is_manual = state->gain_is_manual; @@ -713,6 +729,8 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state exposure = state->exposure; if (output_changed) { + camera_rotation = mod(camera->rotate - device_rotation, 360); + on_output_changed(); } diff --git a/src/process_pipeline.h b/src/process_pipeline.h index 9d0f69b..2413d35 100644 --- a/src/process_pipeline.h +++ b/src/process_pipeline.h @@ -13,6 +13,8 @@ struct mp_process_pipeline_state { int preview_width; int preview_height; + int device_rotation; + bool gain_is_manual; int gain; int gain_max; From b22053dadb0b4ecb72d38567f17a616030f3bc55 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Tue, 4 May 2021 00:34:52 +1000 Subject: [PATCH 18/21] UI rotation follows device --- data/camera.ui | 18 ++++++++++++------ src/main.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/data/camera.ui b/data/camera.ui index c3c8732..23dd058 100644 --- a/data/camera.ui +++ b/data/camera.ui @@ -31,7 +31,8 @@ 0 - horizontal + + 1 1 start 5 @@ -101,10 +102,13 @@ end - horizontal + 1 - end + 1 + center + center 5 + 5 5 5 5 @@ -141,10 +145,12 @@ - horizontal + 1 - end - end + 1 + center + center + 5 5 5 5 diff --git a/src/main.c b/src/main.c index 8458daa..947b651 100644 --- a/src/main.c +++ b/src/main.c @@ -746,6 +746,41 @@ create_simple_action(GtkApplication *app, const char *name, GCallback callback) return action; } +static void update_ui_rotation() +{ + if (device_rotation == 0 || device_rotation == 180) { + // Portrait + gtk_widget_set_halign(preview_top_box, GTK_ALIGN_FILL); + gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_VERTICAL); + + gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_FILL); + gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_HORIZONTAL); + + if (device_rotation == 0) { + gtk_widget_set_valign(preview_top_box, GTK_ALIGN_START); + gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_END); + } else { + gtk_widget_set_valign(preview_top_box, GTK_ALIGN_END); + gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_START); + } + } else { + // Landscape + gtk_widget_set_valign(preview_top_box, GTK_ALIGN_FILL); + gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_HORIZONTAL); + + gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_FILL); + gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_VERTICAL); + + if (device_rotation == 90) { + gtk_widget_set_halign(preview_top_box, GTK_ALIGN_END); + gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_START); + } else { + gtk_widget_set_halign(preview_top_box, GTK_ALIGN_START); + gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_END); + } + } +} + static void display_config_received(GDBusConnection *conn, GAsyncResult *res, gpointer user_data) { GError *error = NULL; @@ -771,6 +806,7 @@ static void display_config_received(GDBusConnection *conn, GAsyncResult *res, gp if (new_rotation != device_rotation) { device_rotation = new_rotation; update_io_pipeline(); + update_ui_rotation(); } } From 59dde6bb74276f34c76d75d5fe801a0666c37b79 Mon Sep 17 00:00:00 2001 From: Martijn Braam Date: Mon, 3 May 2021 18:57:08 +0200 Subject: [PATCH 19/21] Fix crash on musl and clean up dependency --- src/process_pipeline.c | 1 - src/zbar_pipeline.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/process_pipeline.c b/src/process_pipeline.c index 7802ca6..18bc27e 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -14,7 +14,6 @@ #include "gl_util.h" #include -#include #define TIFFTAG_FORWARDMATRIX1 50964 diff --git a/src/zbar_pipeline.c b/src/zbar_pipeline.c index ce86161..0fa5223 100644 --- a/src/zbar_pipeline.c +++ b/src/zbar_pipeline.c @@ -184,7 +184,7 @@ process_image(MPPipeline *pipeline, MPZBarImage **_image) zbar_image_t *zbar_image = zbar_image_create(); zbar_image_set_format(zbar_image, zbar_fourcc('Y', '8', '0', '0')); zbar_image_set_size(zbar_image, width, height); - zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), zbar_image_free_data); + zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), NULL); int res = zbar_scan_image(scanner, zbar_image); assert(res >= 0); From 0046986e3e29e8734f2f06d377f05b58e367211a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 26 Apr 2021 17:57:50 +0200 Subject: [PATCH 20/21] Use g_get_user_config_dir Instead of opencoding rely on glib which handles XDG_CONFIG_HOME and such. This also prevents xdg_config from leaking. --- src/camera_config.c | 13 +------------ src/process_pipeline.c | 17 +++-------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/camera_config.c b/src/camera_config.c index 1e52d62..f8ecf05 100644 --- a/src/camera_config.c +++ b/src/camera_config.c @@ -3,7 +3,6 @@ #include "ini.h" #include "config.h" #include "matrix.h" -#include #include #include #include @@ -21,18 +20,8 @@ static bool find_config(char *conffile) { char buf[512]; - char *xdg_config_home; - wordexp_t exp_result; FILE *fp; - // Resolve XDG stuff - if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { - xdg_config_home = "~/.config"; - } - wordexp(xdg_config_home, &exp_result, 0); - xdg_config_home = strdup(exp_result.we_wordv[0]); - wordfree(&exp_result); - if (access("/proc/device-tree/compatible", F_OK) != -1) { // Reads to compatible string of the current device tree, looks like: // pine64,pinephone-1.2\0allwinner,sun50i-a64\0 @@ -48,7 +37,7 @@ find_config(char *conffile) } // Check for a config file in XDG_CONFIG_HOME - sprintf(conffile, "%s/megapixels/config/%s.ini", xdg_config_home, + sprintf(conffile, "%s/megapixels/config/%s.ini", g_get_user_config_dir (), buf); if (access(conffile, F_OK) != -1) { printf("Found config file at %s\n", conffile); diff --git a/src/process_pipeline.c b/src/process_pipeline.c index 18bc27e..dd5e4f4 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "gl_util.h" @@ -70,20 +69,10 @@ register_custom_tiff_tags(TIFF *tif) static bool find_processor(char *script) { - char *xdg_config_home; char filename[] = "postprocess.sh"; - wordexp_t exp_result; - // Resolve XDG stuff - if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { - xdg_config_home = "~/.config"; - } - wordexp(xdg_config_home, &exp_result, 0); - xdg_config_home = strdup(exp_result.we_wordv[0]); - wordfree(&exp_result); - - // Check postprocess.h in the current working directory - sprintf(script, "data/%s", filename); + // Check postprocess.sh in the current working directory + sprintf(script, "./data/%s", filename); if (access(script, F_OK) != -1) { sprintf(script, "./data/%s", filename); printf("Found postprocessor script at %s\n", script); @@ -91,7 +80,7 @@ find_processor(char *script) } // Check for a script in XDG_CONFIG_HOME - sprintf(script, "%s/megapixels/%s", xdg_config_home, filename); + sprintf(script, "%s/megapixels/%s", g_get_user_config_dir(), filename); if (access(script, F_OK) != -1) { printf("Found postprocessor script at %s\n", script); return true; From e17a516564e7eff3d47523d047225bacd4cc6ad2 Mon Sep 17 00:00:00 2001 From: nwlyoc Date: Sat, 1 May 2021 19:16:56 +0200 Subject: [PATCH 21/21] Copy data from QR code to default clipboard instead of primary selection clipbord because the latter is in no obvious way reachable inside Phosh. --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 947b651..b096e8e 100644 --- a/src/main.c +++ b/src/main.c @@ -496,7 +496,7 @@ on_zbar_dialog_response(GtkDialog *dialog, int response, char *data) { GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(dialog)); gdk_clipboard_set_text( - gdk_display_get_primary_clipboard(display), + gdk_display_get_clipboard(display), data); } case GTK_RESPONSE_CANCEL: