From 7d03c9719e7e01a6a1718fea7d477e80207ee3df Mon Sep 17 00:00:00 2001 From: klemek Date: Mon, 11 May 2026 07:40:20 +0200 Subject: [PATCH] feat: video reconnect (wip egl error 3003) --- src/forge.c | 100 ++++++++++++++++++++++++++++++++---------- src/shaders.c | 117 +++++++++++++++++++++++++++++++++++--------------- src/shaders.h | 8 ++-- src/types.h | 19 +++++--- src/video.c | 30 +++++++------ 5 files changed, 192 insertions(+), 82 deletions(-) diff --git a/src/forge.c b/src/forge.c index 0d47175..58cf919 100644 --- a/src/forge.c +++ b/src/forge.c @@ -4,13 +4,12 @@ #include #include #include -#include +#include #include "types.h" #include "config.h" #include "config_file.h" -#include "file.h" #include "forge.h" #include "midi.h" #include "project.h" @@ -22,11 +21,11 @@ #include "video.h" #include "window.h" +static Parameters init_params; static SharedContext *context; static ShaderProgram program; static Window *window_output; static Window *window_monitor; -static VideoCaptureArray inputs; static Timer timer; static MidiDevice midi; static bool trace_midi; @@ -66,9 +65,6 @@ static void init_context(const Parameters *params) { params->auto_random_cycle, params->base_tempo, params->load_state); memset(context->input_resolutions, 0, sizeof(context->input_resolutions)); - memset(context->input_formats, 0, sizeof(context->input_formats)); - memset(context->input_fps, 0, sizeof(context->input_fps)); - memset(context->input_swap, 0, sizeof(context->input_swap)); } static void free_context() { shared_close_context(context); } @@ -79,36 +75,79 @@ static void reload_shader(unsigned int i) { #ifdef VIDEO_IN static void init_inputs(const StringArray *video_in, unsigned int video_size) { - inputs.length = video_in->length; + context->inputs.length = video_in->length; for (unsigned int i = 0; i < video_in->length; i++) { - video_init(&inputs.values[i], video_in->values[i], video_size); + video_init(&context->inputs.values[i], video_in->values[i], video_size); - if (!inputs.values[i].error) { - context->input_resolutions[i][0] = inputs.values[i].width; - context->input_resolutions[i][1] = inputs.values[i].height; - context->input_formats[i] = inputs.values[i].pixelformat; + if (!context->inputs.values[i].error) { + context->input_resolutions[i][0] = context->inputs.values[i].width; + context->input_resolutions[i][1] = context->inputs.values[i].height; + context->inputs.values[i].needs_reload = false; + context->inputs.values[i].disconnected = false; } } } -static bool start_video_captures(unsigned int video_count, bool trace_fps) { - for (unsigned int i = 0; i < video_count; i++) { - if (!inputs.values[i].error && - !video_background_read(&inputs.values[i], context, i, trace_fps)) { - return false; +static bool reconnect_video_captures(const StringArray *video_in, + unsigned int video_size, bool trace_fps) { + for (unsigned int i = 0; i < video_in->length; i++) { + if (context->inputs.values[i].disconnected) { + video_init(&context->inputs.values[i], video_in->values[i], video_size); + + if (!context->inputs.values[i].error) { + context->input_resolutions[i][0] = context->inputs.values[i].width; + context->input_resolutions[i][1] = context->inputs.values[i].height; + + if (!video_background_read(&context->inputs.values[i], context, i, + trace_fps)) { + return false; + } + + context->inputs.values[i].needs_reload = true; + context->inputs.values[i].disconnected = false; + } } } return true; } +static bool start_video_captures(unsigned int video_count, bool trace_fps) { + pid_t pid; + for (unsigned int i = 0; i < video_count; i++) { + if (!context->inputs.values[i].error && + !video_background_read(&context->inputs.values[i], context, i, + trace_fps)) { + return false; + } + } + pid = fork(); + if (pid < 0) { + log_error("Could not create subprocess"); + return false; + } + if (pid == 0) { + return true; + } + log_info("background reconnect acquisition started (pid: %d)", pid); + while (!context->stop) { + sleep(1); + if (!reconnect_video_captures(&init_params.video_in, init_params.video_size, + init_params.trace_fps)) { + return false; + } + } + exit(EXIT_SUCCESS); +} + static void free_video_captures(unsigned int video_count) { for (unsigned int i = 0; i < video_count; i++) { - shaders_free_input(&program, &inputs.values[i]); + shaders_free_input(&program, i); - video_free(&inputs.values[i]); + video_free(&context->inputs.values[i]); } } + #endif /* VIDEO_IN */ static void error_callback(int error, const char *description) { @@ -140,6 +179,8 @@ static void midi_callback(unsigned char code, unsigned char value) { } static bool init(const Parameters *params) { + init_params = *params; + project_init(&project, params->project_path, params->config_file); if (project.error) { @@ -200,7 +241,7 @@ static bool init(const Parameters *params) { } #ifdef VIDEO_IN - shaders_link_inputs(&program, &project, &inputs); + shaders_link_inputs(&program, &project, &context->inputs); #endif /* VIDEO_IN */ if (program.error) { @@ -221,11 +262,20 @@ static bool should_close() { (window_monitor != NULL && window_should_close(window_monitor)); } -static void loop(bool hr, bool trace_fps) { +static bool loop(bool hr, bool trace_fps) { if (hr) { project_reload(&project, reload_shader); } +#ifdef VIDEO_IN + for (unsigned int i = 0; i < context->inputs.length; i++) { + if (context->inputs.values[i].needs_reload) { + shaders_relink_input(&program, &project, &context->inputs, i); + context->inputs.values[i].needs_reload = false; + } + } +#endif /* VIDEO_IN */ + compute_fps(trace_fps); context->time = window_get_time(); @@ -248,6 +298,8 @@ static void loop(bool hr, bool trace_fps) { } window_events(); + + return true; } static void shutdown(const Parameters *params) { @@ -288,8 +340,10 @@ void forge_run(const Parameters *params) { } while (!should_close()) { - loop(params->hot_reload, params->trace_fps); + if (!loop(params->hot_reload, params->trace_fps)) { + return; + } } shutdown(params); -} \ No newline at end of file +} diff --git a/src/shaders.c b/src/shaders.c index 5ff5ed2..7b66158 100644 --- a/src/shaders.c +++ b/src/shaders.c @@ -39,6 +39,20 @@ bool check_glerror(ShaderProgram *program) { return code > 0; } +bool check_eglerror(ShaderProgram *program, const char *context) { + unsigned int code; + + code = eglGetError(); + + if (code > 0 && code != EGL_SUCCESS) { + log_warn("(%s) EGL Error: %04x", context, code); + program->error = true; + return true; + } + + return false; +} + static void init_gl(ShaderProgram *program) { gladLoadGL(glfwGetProcAddress); @@ -88,7 +102,15 @@ static void rebind_textures(const ShaderProgram *program) { #ifdef VIDEO_IN static void link_input_to_texture(ShaderProgram *program, VideoCapture *input, - unsigned int texture_index, bool swap) { + unsigned int input_index, + unsigned int texture_index, bool swap, + bool reload) { + if (reload) { + glDeleteTextures(1, program->textures + texture_index); + + glGenTextures(1, program->textures + texture_index); + } + EGLImageKHR dma_image; dma_image = EGL_NO_IMAGE_KHR; @@ -111,15 +133,14 @@ static void link_input_to_texture(ShaderProgram *program, VideoCapture *input, dma_image = eglCreateImageKHR(program->egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attrib_list); - if (swap) { - input->dma_image_swap = dma_image; - } else { - input->dma_image = dma_image; + if (check_eglerror(program, "eglCreateImageKHR")) { + return; } - if (dma_image == EGL_NO_IMAGE_KHR) { - log_error("(%s) eglCreateImageKHR failed %04x", input->name, eglGetError()); - return; + if (swap) { + program->dma_images_swap[input_index] = dma_image; + } else { + program->dma_images[input_index] = dma_image; } glActiveTexture(GL_TEXTURE0 + texture_index); @@ -130,30 +151,35 @@ static void link_input_to_texture(ShaderProgram *program, VideoCapture *input, GL_UNSIGNED_BYTE, 0); // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_EGL_image_storage.txt - glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, input->dma_image, NULL); + glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, dma_image, NULL); + + if (check_eglerror(program, "glEGLImageTargetTexStorageEXT")) { + return; + } log_info("Texture %d linked to %s", texture_index, input->name); } static void init_input(ShaderProgram *program, const ConfigFile *config, - VideoCaptureArray *inputs) { + VideoCaptureArray *inputs, unsigned int i, bool reload) { unsigned int tex_i; char name[STR_LEN]; - for (unsigned int i = 0; i < program->in_count; i++) { - if (i < inputs->length && !inputs->values[i].error) { - snprintf(name, STR_LEN, "IN_%d_OUT", i + 1); + if (i < inputs->length && !inputs->values[i].error) { + snprintf(name, STR_LEN, "IN_%d_OUT", i + 1); + tex_i = config_file_get_int(config, name, 0); + link_input_to_texture(program, &inputs->values[i], i, tex_i, false, reload); + if (inputs->values[i].with_swap) { + snprintf(name, STR_LEN, "IN_%d_SWAP_OUT", i + 1); tex_i = config_file_get_int(config, name, 0); - link_input_to_texture(program, &inputs->values[i], tex_i, false); - if (inputs->values[i].with_swap) { - snprintf(name, STR_LEN, "IN_%d_SWAP_OUT", i + 1); - tex_i = config_file_get_int(config, name, 0); - link_input_to_texture(program, &inputs->values[i], tex_i, true); - } - } else { - log_warn("Cannot link input %d", i + 1); + link_input_to_texture(program, &inputs->values[i], i, tex_i, true, + reload); } + } else { + log_warn("Cannot link input %d", i + 1); } + + check_glerror(program); } #endif /* VIDEO_IN */ @@ -426,8 +452,10 @@ static void update_viewport(ShaderProgram *program, // clean and resize all textures for (unsigned int i = 0; i < program->tex_count; i++) { glActiveTexture(GL_TEXTURE0 + i); + // TODO dont remap input textures glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, context->tex_resolution[0], context->tex_resolution[1], 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + check_glerror(program); } program->last_resolution[0] = context->resolution[0]; program->last_resolution[1] = context->resolution[1]; @@ -507,11 +535,11 @@ static void use_program(const ShaderProgram *program, int i, bool output, write_uniform_2f(program->iinres_locations[i * program->in_count + j], &context->input_resolutions[j]); write_uniform_1i(program->iinfmt_locations[i * program->in_count + j], - context->input_formats[j]); + context->inputs.values[j].pixelformat); write_uniform_1i(program->iinfps_locations[i * program->in_count + j], - context->input_fps[j]); + context->inputs.values[j].fps); write_uniform_1i(program->iinswap_locations[i * program->in_count + j], - context->input_swap[j] ? 1 : 0); + context->inputs.values[j].swap ? 1 : 0); } // set seeds uniforms @@ -581,6 +609,11 @@ void shaders_init(ShaderProgram *program, const Project *project, program->active_count = project->state_config.midi_active_counts.length; program->midi_lengths.length = 0; +#ifdef VIDEO_IN + memset(program->dma_images, 0, sizeof(program->dma_images)); + memset(program->dma_images_swap, 0, sizeof(program->dma_images_swap)); +#endif /* VIDEO_IN */ + init_gl(program); if (check_glerror(program)) { @@ -625,13 +658,19 @@ void shaders_init(ShaderProgram *program, const Project *project, } } +void shaders_relink_input(ShaderProgram *program, const Project *project, + VideoCaptureArray *inputs, unsigned int i) { +#ifdef VIDEO_IN + // shaders_free_input(program, i); + init_input(program, &project->config, inputs, i, true); +#endif /* VIDEO_IN */ +} + void shaders_link_inputs(ShaderProgram *program, const Project *project, VideoCaptureArray *inputs) { #ifdef VIDEO_IN - init_input(program, &project->config, inputs); - - if (check_glerror(program)) { - return; + for (unsigned int i = 0; i < program->in_count; i++) { + init_input(program, &project->config, inputs, i, false); } #endif /* VIDEO_IN */ } @@ -688,14 +727,22 @@ void shaders_free_window(const ShaderProgram *program, bool secondary) { glDeleteVertexArrays(1, &program->vertex_array[secondary ? 1 : 0]); } -void shaders_free_input(const ShaderProgram *program, - const VideoCapture *input) { +void shaders_free_input(ShaderProgram *program, unsigned int input_index) { #ifdef VIDEO_IN - if (!input->error && input->dma_image != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(program->egl_display, input->dma_image); + if (program->dma_images[input_index] != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(program->egl_display, program->dma_images[input_index]); + + program->dma_images[input_index] = EGL_NO_IMAGE_KHR; + + check_eglerror(program, "eglDestroyImageKHR"); } - if (!input->error && input->dma_image_swap != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(program->egl_display, input->dma_image_swap); + if (program->dma_images_swap[input_index] != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(program->egl_display, + program->dma_images_swap[input_index]); + + program->dma_images_swap[input_index] = EGL_NO_IMAGE_KHR; + + check_eglerror(program, "eglDestroyImageKHR"); } #endif /* VIDEO_IN */ -} \ No newline at end of file +} diff --git a/src/shaders.h b/src/shaders.h index 646103c..347a938 100644 --- a/src/shaders.h +++ b/src/shaders.h @@ -6,6 +6,9 @@ void shaders_init(ShaderProgram *program, const Project *project, const SharedContext *context, bool rebind); +void shaders_relink_input(ShaderProgram *program, const Project *project, + VideoCaptureArray *inputs, unsigned int i); + void shaders_link_inputs(ShaderProgram *program, const Project *project, VideoCaptureArray *inputs); @@ -19,7 +22,6 @@ void shaders_free(const ShaderProgram *program); void shaders_free_window(const ShaderProgram *program, bool secondary); -void shaders_free_input(const ShaderProgram *program, - const VideoCapture *input); +void shaders_free_input(ShaderProgram *program, unsigned int input_index); -#endif /* SHADERS_H */ \ No newline at end of file +#endif /* SHADERS_H */ diff --git a/src/types.h b/src/types.h index d8fc727..e158b57 100644 --- a/src/types.h +++ b/src/types.h @@ -130,6 +130,8 @@ typedef struct ShaderProgram { unsigned int in_count; #ifdef VIDEO_IN EGLDisplay egl_display; + EGLImageKHR dma_images[MAX_VIDEO]; + EGLImageKHR dma_images_swap[MAX_VIDEO]; #endif /* VIDEO_IN */ } ShaderProgram; @@ -138,7 +140,11 @@ typedef struct ShaderProgram { typedef struct VideoCapture { char name[STR_LEN]; bool error; + bool disconnected; + bool needs_reload; bool with_swap; + bool swap; + unsigned int fps; int fd; int exp_fd; int exp_fd_swap; @@ -149,12 +155,13 @@ typedef struct VideoCapture { #ifdef VIDEO_IN struct v4l2_buffer buf; struct v4l2_buffer buf_swap; - EGLImageKHR dma_image; - EGLImageKHR dma_image_swap; #endif /* VIDEO_IN */ } VideoCapture; -typedef ARRAY(VideoCaptureArray, VideoCapture); +typedef struct VideoCaptureArray { + VideoCapture values[MAX_VIDEO]; + unsigned int length; +} VideoCaptureArray; // window.c @@ -193,9 +200,7 @@ typedef struct SharedContext { unsigned int auto_random_cycle; unsigned int seeds[MAX_FRAG]; unsigned int fps; - unsigned int input_formats[MAX_VIDEO]; - unsigned int input_fps[MAX_VIDEO]; - bool input_swap[MAX_VIDEO]; + VideoCaptureArray inputs; bool stop; } SharedContext; @@ -277,4 +282,4 @@ typedef struct Project { File fragment_shaders[MAX_FRAG][MAX_SUB_FILE + 1]; } Project; -#endif /* TYPES_H */ \ No newline at end of file +#endif /* TYPES_H */ diff --git a/src/video.c b/src/video.c index 1aab9ba..458442c 100644 --- a/src/video.c +++ b/src/video.c @@ -1,6 +1,5 @@ #ifdef VIDEO_IN -#include #include #include #include @@ -286,22 +285,25 @@ static void close_stream(const VideoCapture *video_capture) { ioctl(video_capture->fd, VIDIOC_STREAMOFF, &buf_type); } -static unsigned int read_video(const VideoCapture *video_capture, bool swap) { - int result; +static unsigned int read_video(VideoCapture *video_capture) { + unsigned int result; + struct v4l2_capability cap; result = 0; - if ((swap || !video_capture->with_swap) && + if ((video_capture->swap || !video_capture->with_swap) && ioctl(video_capture->fd, VIDIOC_DQBUF, &video_capture->buf) != -1) { ioctl(video_capture->fd, VIDIOC_QBUF, &video_capture->buf); result = 1; - } else if (!swap && video_capture->with_swap && + } else if (!video_capture->swap && video_capture->with_swap && ioctl(video_capture->fd, VIDIOC_DQBUF, &video_capture->buf_swap) != -1) { ioctl(video_capture->fd, VIDIOC_QBUF, &video_capture->buf_swap); result = 2; + } else if (ioctl(video_capture->fd, VIDIOC_QUERYCAP, &cap) == -1) { + video_capture->error = true; } return result; @@ -361,18 +363,18 @@ bool video_background_read(VideoCapture *video_capture, SharedContext *context, pid); timer_init(&timer, 30); - while (!context->stop) { - video_result = read_video(video_capture, context->input_swap[input_index]); + while (!context->stop && !video_capture->error) { + video_result = read_video(video_capture); if (video_result > 0 && timer_inc(&timer)) { fps = timer_reset(&timer); - context->input_fps[input_index] = (unsigned int)round(fps); + context->inputs.values[input_index].fps = (unsigned int)round(fps); if (trace_fps) { log_trace("(%s) %.2ffps", video_capture->name, fps); } } if (video_result > 0) { - context->input_swap[input_index] = video_result == 2; + video_capture->swap = video_result == 2; } } if (context->stop) { @@ -381,15 +383,15 @@ bool video_background_read(VideoCapture *video_capture, SharedContext *context, } else { log_info("(%s) background acquisition stopped after error (pid: %d)", video_capture->name, pid); + video_capture->disconnected = true; + video_capture->pixelformat = 0; + video_free(video_capture); } exit(context->stop ? EXIT_SUCCESS : EXIT_FAILURE); - return false; } void video_free(const VideoCapture *video_capture) { - if (!video_capture->error) { - close_stream(video_capture); - } + close_stream(video_capture); if (video_capture->exp_fd != -1) { close(video_capture->exp_fd); } @@ -401,4 +403,4 @@ void video_free(const VideoCapture *video_capture) { } } -#endif /* VIDEO_IN */ \ No newline at end of file +#endif /* VIDEO_IN */