feat: video reconnect (wip egl error 3003)

This commit is contained in:
2026-05-11 07:40:20 +02:00
parent dd20515e2b
commit 7d03c9719e
5 changed files with 192 additions and 82 deletions
+77 -23
View File
@@ -4,13 +4,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#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);
}
}
+82 -35
View File
@@ -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 */
}
}
+5 -3
View File
@@ -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 */
#endif /* SHADERS_H */
+12 -7
View File
@@ -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 */
#endif /* TYPES_H */
+16 -14
View File
@@ -1,6 +1,5 @@
#ifdef VIDEO_IN
#include <bsd/string.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
@@ -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 */
#endif /* VIDEO_IN */