diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62cb9ec..5f71cc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: install libs run: sudo apt install -y libglfw3-dev libgl-dev libv4l-dev libasound2-dev - name: gcc - run: mkdir -p build && gcc -v -Wall -Wextra -Werror src/*.c src/*.h -lglfw -lGL -lm -lasound -Iinclude hashmap.c/hashmap.c log.c/src/log.c -DGLFW_INCLUDE_NONE -DGLFW_EXPOSE_NATIVE_EGL -DGLFW_NATIVE_INCLUDE_NONE + run: mkdir -p build && gcc -v -Wall -Wextra -Werror -Wno-format-truncation src/*.c src/*.h -lglfw -lGL -lm -lasound -Iinclude hashmap.c/hashmap.c log.c/src/log.c -DGLFW_INCLUDE_NONE -DGLFW_EXPOSE_NATIVE_EGL -DGLFW_NATIVE_INCLUDE_NONE build-release: needs: lint diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 1105a2d..bbb5047 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -85,6 +85,7 @@ make -f Makefile.dev release-arch - [x] Clean code and fix things - [x] Share openGL state between monitor and screen - [ ] Default project + - [ ] split with includes - [ ] src 9: game of life - [ ] src 10 : ? - [ ] src 11 : ? @@ -97,7 +98,9 @@ make -f Makefile.dev release-arch - [ ] Other - [x] `forge_project.cfg` - [x] Define frag prefix in config - - [ ] Use custom `#include xxx.glsl` preprocessor + - [x] Use custom `#include xxx.glsl` preprocessor + - [ ] Use snprintf isntead of sprintf (and strlcpy instand of strncpy) + - [ ] Pass "heavy" struct as pointer to avoid stack overload - [x] Clean and sort args - [x] `--auto-random` / `--no-auto-random` - [ ] Update readme with usage documentation diff --git a/Makefile.dev b/Makefile.dev index 9fbc063..8a3023f 100644 --- a/Makefile.dev +++ b/Makefile.dev @@ -16,6 +16,7 @@ build: log.c/src/log.c \ -lm -lGL -lglfw -lasound \ -Wall -Wextra \ + -Wno-format-truncation \ -DGLFW_INCLUDE_NONE \ -DGLFW_EXPOSE_NATIVE_EGL \ -DGLFW_NATIVE_INCLUDE_NONE \ @@ -31,6 +32,10 @@ run: build demo: build ./build/$(TARGET) $(TEST_ARGS) --demo +.PHONY: sample +sample: build + ./build/$(TARGET) --project=sample + .PHONY: valgrind valgrind: build valgrind \ diff --git a/README.md b/README.md index a8e043e..8530722 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ b - [NanoKontrol2 Controller mapping](#nanokontrol2-controller-mapping) - [Making your own FORGE project](#making-your-own-forge-project) - [`forge_project.cfg`](#forge_projectcfg) - - [Working with `frag0.glsl`](#working-with-frag0glsl) + - [Writing your fragment shaders](#writing-your-fragment-shaders) + - [Working with `#include`](#working-with-include) - [Frequently Asked Questions](#frequently-asked-questions) - [Why "steel"?](#why-steel) - [How do I report a bug?](#how-do-i-report-a-bug) @@ -178,7 +179,11 @@ TODO TODO -### Working with `frag0.glsl` +### Writing your fragment shaders + +TODO + +### Working with `#include` TODO diff --git a/default/frag1.glsl b/default/frag1.glsl index 0b5aebe..c5a3ebc 100644 --- a/default/frag1.glsl +++ b/default/frag1.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // VIDEO 1 // ----------- // IN: 1 (RAW IN A) diff --git a/default/frag10.glsl b/default/frag10.glsl index bdc3600..4e2d812 100644 --- a/default/frag10.glsl +++ b/default/frag10.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // MONITOR // --- diff --git a/default/frag2.glsl b/default/frag2.glsl index 84513e8..c485173 100644 --- a/default/frag2.glsl +++ b/default/frag2.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // VIDEO 2 // ----------- // IN: 2 (RAW IN B) diff --git a/default/frag3.glsl b/default/frag3.glsl index 68caa17..d53b640 100644 --- a/default/frag3.glsl +++ b/default/frag3.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // SRC A // ----------- // OUT: 5 (FX A) diff --git a/default/frag4.glsl b/default/frag4.glsl index 5c47eb5..1035c9e 100644 --- a/default/frag4.glsl +++ b/default/frag4.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // SRC B // ----------- // OUT: 6 (FX B) diff --git a/default/frag5.glsl b/default/frag5.glsl index 85c9145..bb3d983 100644 --- a/default/frag5.glsl +++ b/default/frag5.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // FX A // ------------- // IN: 5 (SRC A) diff --git a/default/frag6.glsl b/default/frag6.glsl index 41ec53d..546ca27 100644 --- a/default/frag6.glsl +++ b/default/frag6.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // FX B // ------------- // IN: 6 (SRC B) diff --git a/default/frag7.glsl b/default/frag7.glsl index 8f4b2d2..e60ffd4 100644 --- a/default/frag7.glsl +++ b/default/frag7.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // A+B // ------------ // IN: 7 (FX A) diff --git a/default/frag8.glsl b/default/frag8.glsl index 4d36f15..ad1b153 100644 --- a/default/frag8.glsl +++ b/default/frag8.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // MFX // ------------ // IN: 9 (A+B) diff --git a/default/frag9.glsl b/default/frag9.glsl index 8316f91..eac9116 100644 --- a/default/frag9.glsl +++ b/default/frag9.glsl @@ -1,3 +1,5 @@ +#include frag0.glsl + // OUT // --- diff --git a/src/config.h b/src/config.h index 8213789..e252110 100644 --- a/src/config.h +++ b/src/config.h @@ -31,6 +31,10 @@ #define MAX_FRAG 64 #endif +#ifndef MAX_SUB_FILE +#define MAX_SUB_FILE 32 +#endif + /* MIDI */ #ifndef UNSET_MIDI_CODE @@ -44,7 +48,7 @@ /* ARRAY */ #ifndef ARRAY_SIZE -#define ARRAY_SIZE 1024 +#define ARRAY_SIZE 512 #endif #ifndef ARRAY_NOT_FOUND diff --git a/src/file.c b/src/file.c index d09e0e0..c587926 100644 --- a/src/file.c +++ b/src/file.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ bool file_should_update(File file) { return file.last_write != get_file_time(file); } -void file_update(File *file) { +bool file_update(File *file) { long length; FILE *file_pointer; @@ -40,19 +41,19 @@ void file_update(File *file) { if (file_pointer == NULL) { file->error = true; log_error("Cannot open file '%s'", file->path); - return; + return false; } // read file length fseek(file_pointer, 0, SEEK_END); length = ftell(file_pointer); // init buffer fseek(file_pointer, 0, SEEK_SET); - file->content = (char *)malloc((length + 1) * sizeof(char)); + file->content = malloc(length + 1); if (file->content == NULL) { file->error = true; fclose(file_pointer); log_error("Cannot read file '%s'", file->path); - return; + return false; } // read file fread(file->content, sizeof(char), length, file_pointer); @@ -62,12 +63,14 @@ void file_update(File *file) { file->content[length] = '\0'; // read last update time file->last_write = get_file_time(*file); + + return true; } File file_read(char *path) { File file; - sprintf(file.path, path, STR_LEN); + strncpy(file.path, path, STR_LEN); file.content = NULL; file.error = false; file.last_write = 0; @@ -98,12 +101,17 @@ void file_write(char *path, StringArray lines) { fclose(file_pointer); } -void file_prepend(File *src, File extra) { - char *old_src_content; - - old_src_content = src->content; - src->content = string_concat(extra.content, src->content); - free(old_src_content); +void file_free(File *file) { + if (!file->error) { + free(file->content); + file->error = true; + } } -void file_free(File *file) { free(file->content); } \ No newline at end of file +void file_free_array(FileArray *files) { + unsigned int i; + + for (i = 0; i < files->length; i++) { + file_free(&files->values[i]); + } +} \ No newline at end of file diff --git a/src/file.h b/src/file.h index daca753..c9c373c 100644 --- a/src/file.h +++ b/src/file.h @@ -7,12 +7,12 @@ File file_read(char *path); bool file_should_update(File file); -void file_update(File *file); - -void file_prepend(File *src, File extra); +bool file_update(File *file); void file_write(char *path, StringArray lines); void file_free(File *file); +void file_free_array(FileArray *files); + #endif /* FILE_H */ \ No newline at end of file diff --git a/src/forge.c b/src/forge.c index ba751af..8cb21c4 100644 --- a/src/forge.c +++ b/src/forge.c @@ -81,7 +81,7 @@ static void init_context(Parameters params, unsigned int in_count) { static void free_context() { shared_close_context(context); } static void reload_shader(unsigned int i) { - shaders_update(program, project.fragment_shaders, i); + shaders_update(program, project.fragment_shaders[i][0], i); } static void init_inputs(StringArray video_in, unsigned int video_size) { @@ -186,7 +186,11 @@ void forge_run(Parameters params) { context->stop = false; - project = project_init(params.project_path, params.config_file); + project_init(&project, params.project_path, params.config_file); + + if (project.error) { + return; + } init_inputs(params.video_in, params.video_size); @@ -222,7 +226,7 @@ void forge_run(Parameters params) { window_use(window_output, context); - program = shaders_init(project, context, inputs, NULL); + shaders_init(&program, &project, context, inputs, false); } else { window_output = NULL; } @@ -234,8 +238,7 @@ void forge_run(Parameters params) { window_use(window_monitor, context); - program = shaders_init(project, context, inputs, - window_output != NULL ? &program : NULL); + shaders_init(&program, &project, context, inputs, window_output != NULL); } else { window_monitor = NULL; } @@ -279,7 +282,7 @@ void forge_run(Parameters params) { free_context(); - project_free(project); + project_free(&project); window_terminate(); } \ No newline at end of file diff --git a/src/project.c b/src/project.c index ec1b5e5..a64a83f 100644 --- a/src/project.c +++ b/src/project.c @@ -1,95 +1,142 @@ +#include + #include "types.h" +#include "config.h" #include "config_file.h" #include "file.h" +#include "log.h" #include "project.h" #include "state.h" +#include "string.h" -static File read_fragment_shader_file(char *frag_path, char *frag_prefix, - unsigned int i) { - File fragment_shader; +static bool parse_fragment_shader_file(Project *project, unsigned int i) { + File tmp_file, fragment_shader; char file_path[STR_LEN]; + char *include_pos, *include_end; + char included_file[STR_LEN]; + char *new_content; - snprintf(file_path, STR_LEN, "%s/%s%d.glsl", frag_path, frag_prefix, i); - fragment_shader = file_read(file_path); - if (fragment_shader.error) { - exit(EXIT_FAILURE); + fragment_shader = project->fragment_shaders[i][0]; + + project->sub_counts.values[i] = 0; + + include_pos = strstr(fragment_shader.content, "#include "); + + while (include_pos != NULL && project->sub_counts.values[i] < MAX_SUB_FILE) { + include_end = strstr(include_pos, "\n"); + if (include_end == NULL) { + log_error("Invalid '#include' directive in '%s'", fragment_shader.path); + return false; + } + + strlcpy(included_file, include_pos + 9, include_end - include_pos - 8); + + snprintf(file_path, STR_LEN, "%s/%s", project->path, included_file); + + tmp_file = file_read(file_path); + + if (tmp_file.error) { + return false; + } + + project->fragment_shaders[i][++project->sub_counts.values[i]] = tmp_file; + + new_content = string_replace_at( + fragment_shader.content, include_pos - fragment_shader.content, + include_end - fragment_shader.content, tmp_file.content); + + project->fragment_shaders[i][0].content = new_content; + + file_free(&tmp_file); + + include_pos = strstr(fragment_shader.content, "#include "); } - return fragment_shader; + return true; } -static void init_files(Project *output, char *frag_path, char *frag_prefix, - unsigned int frag_count) { +static bool read_fragment_shader_file(Project *project, char *frag_prefix, + unsigned int i) { + char file_path[STR_LEN]; + + snprintf(file_path, STR_LEN, "%s/%s%d.glsl", project->path, frag_prefix, + i + 1); + + project->fragment_shaders[i][0] = file_read(file_path); + + if (project->fragment_shaders[i][0].error) { + return false; + } + + return parse_fragment_shader_file(project, i); +} + +void project_init(Project *project, char *project_path, char *config_file) { + char config_path[STR_LEN]; + char *frag_prefix; unsigned int i; - output->fragment_shaders.length = frag_count; + strlcpy(project->path, project_path, STR_LEN); - for (i = 0; i < frag_count + 1; i++) { - if (i == 0) { - output->common_shader_code = - read_fragment_shader_file(frag_path, frag_prefix, i); - } else { - output->fragment_shaders.values[i - 1] = - read_fragment_shader_file(frag_path, frag_prefix, i); + snprintf(config_path, STR_LEN, "%s/%s", project_path, config_file); - file_prepend(&output->fragment_shaders.values[i - 1], - output->common_shader_code); + project->config = config_file_read(config_path); + + project->state_config = state_parse_config(project->config); + + project->frag_count = config_file_get_int(project->config, "FRAG_COUNT", 1); + project->in_count = config_file_get_int(project->config, "IN_COUNT", 0); + frag_prefix = + config_file_get_str(project->config, "FRAG_FILE_PREFIX", "frag"); + + if (project->frag_count > MAX_FRAG) { + log_error("FRAG_COUNT over %d", MAX_FRAG); + project->error = true; + return; + } + + project->sub_counts.length = project->frag_count; + + for (i = 0; i < project->frag_count; i++) { + project->sub_counts.values[i] = 0; + if (!read_fragment_shader_file(project, frag_prefix, i)) { + project_free(project); + project->error = true; + return; } } } -Project project_init(char *project_path, char *config_file) { - Project project; - char config_path[STR_LEN]; - char *frag_prefix; - - snprintf(config_path, STR_LEN, "%s/%s", project_path, config_file); - - project.config = config_file_read(config_path); - - project.state_config = state_parse_config(project.config); - - project.frag_count = config_file_get_int(project.config, "FRAG_COUNT", 1); - project.in_count = config_file_get_int(project.config, "IN_COUNT", 0); - frag_prefix = config_file_get_str(project.config, "FRAG_FILE_PREFIX", "frag"); - - init_files(&project, project_path, frag_prefix, project.frag_count); - - return project; -} - void project_reload(Project *project, void (*reload_callback)(unsigned int)) { - unsigned int i; - bool force_update; - - force_update = false; - - if (file_should_update(project->common_shader_code)) { - file_update(&project->common_shader_code); - force_update = true; - } + unsigned int i, j; + bool should_update; for (i = 0; i < project->frag_count; i++) { - if (force_update || - file_should_update(project->fragment_shaders.values[i])) { - file_update(&project->fragment_shaders.values[i]); - file_prepend(&project->fragment_shaders.values[i], - project->common_shader_code); + should_update = file_should_update(project->fragment_shaders[i][0]); + for (j = 0; j < project->sub_counts.values[i]; j++) { + should_update = should_update || + file_should_update(project->fragment_shaders[i][j + 1]); + } + + should_update = + should_update && file_update(&project->fragment_shaders[i][0]); + + should_update = should_update && parse_fragment_shader_file(project, i); + + if (should_update) { reload_callback(i); } } } -void project_free(Project project) { +void project_free(Project *project) { unsigned int i; - for (i = 0; i < project.frag_count; i++) { - file_free(&project.fragment_shaders.values[i]); + for (i = 0; i < project->frag_count; i++) { + file_free(&project->fragment_shaders[i][0]); } - file_free(&project.common_shader_code); - - config_file_free(project.config); + config_file_free(project->config); } \ No newline at end of file diff --git a/src/project.h b/src/project.h index cd295ef..cca35a4 100644 --- a/src/project.h +++ b/src/project.h @@ -3,10 +3,10 @@ #ifndef PROJECT_H #define PROJECT_H -Project project_init(char *project_path, char *config_file); +void project_init(Project *project, char *project_path, char *config_file); void project_reload(Project *project, void (*reload_callback)(unsigned int)); -void project_free(Project project); +void project_free(Project *project); #endif /* PROJECT_H */ \ No newline at end of file diff --git a/src/shaders.c b/src/shaders.c index 4bafe3c..6c3eaa8 100644 --- a/src/shaders.c +++ b/src/shaders.c @@ -213,7 +213,7 @@ static bool compile_shader(GLuint shader_id, char *name, char *source_code) { return status_params == GL_TRUE; } -static void init_shaders(ShaderProgram *program, FileArray fragment_shaders) { +static void init_shaders(ShaderProgram *program, Project *project) { unsigned int i; // compile vertex shader @@ -225,8 +225,8 @@ static void init_shaders(ShaderProgram *program, FileArray fragment_shaders) { for (i = 0; i < program->frag_count; i++) { program->fragment_shaders[i] = glCreateShader(GL_FRAGMENT_SHADER); program->error |= !compile_shader(program->fragment_shaders[i], - fragment_shaders.values[i].path, - fragment_shaders.values[i].content); + project->fragment_shaders[i][0].path, + project->fragment_shaders[i][0].content); if (program->error) { return; @@ -377,69 +377,58 @@ static void init_programs(ShaderProgram *program, ConfigFile config, } } -ShaderProgram shaders_init(Project project, SharedContext *context, - VideoCaptureArray inputs, ShaderProgram *previous) { - ShaderProgram program; +void shaders_init(ShaderProgram *program, Project *project, + SharedContext *context, VideoCaptureArray inputs, + bool rebind) { + if (!rebind) { + program->error = false; + program->last_resolution[0] = context->resolution[0]; + program->last_resolution[1] = context->resolution[1]; + program->tex_count = config_file_get_int(project->config, "TEX_COUNT", 9); + program->frag_count = project->frag_count; + program->frag_output_index = + config_file_get_int(project->config, "FRAG_OUTPUT", 1) - 1; + program->frag_monitor_index = + config_file_get_int(project->config, "FRAG_MONITOR", 1) - 1; + program->sub_type_count = + config_file_get_int(project->config, "SUB_TYPE_COUNT", 0); + program->in_count = config_file_get_int(project->config, "IN_COUNT", 0); + program->sub_variant_count = project->state_config.state_max; + program->active_count = project->state_config.midi_active_counts.length; + program->midi_lengths.length = 0; - if (previous == NULL) { - program.error = false; - program.last_resolution[0] = context->resolution[0]; - program.last_resolution[1] = context->resolution[1]; - program.tex_count = config_file_get_int(project.config, "TEX_COUNT", 9); - program.frag_count = project.frag_count; - program.frag_output_index = - config_file_get_int(project.config, "FRAG_OUTPUT", 1) - 1; - program.frag_monitor_index = - config_file_get_int(project.config, "FRAG_MONITOR", 1) - 1; - program.sub_type_count = - config_file_get_int(project.config, "SUB_TYPE_COUNT", 0); - program.in_count = config_file_get_int(project.config, "IN_COUNT", 0); - program.sub_variant_count = project.state_config.state_max; - program.active_count = project.state_config.midi_active_counts.length; - program.midi_lengths.length = 0; + init_gl(program); - if (program.frag_count > MAX_FRAG) { - log_error("FRAG_COUNT over %d", MAX_FRAG); - program.error = true; - return program; + init_shaders(program, project); + + if (program->error) { + return; } - init_gl(&program); + init_textures(program, context); - init_shaders(&program, project.fragment_shaders); + init_input(program, project->config, inputs); - if (program.error) { - return program; - } + init_framebuffers(program, project->config); - init_textures(&program, context); + init_programs(program, project->config, project->state_config); - init_input(&program, project.config, inputs); - - init_framebuffers(&program, project.config); - - init_programs(&program, project.config, project.state_config); - - init_vertices(&program); + init_vertices(program); // log_debug("Error after init: %04x", // glGetError()); // TODO check error at each step - } else { - program = *previous; } - bind_vertices(&program, previous != NULL ? 1 : 0); - - return program; + bind_vertices(program, rebind ? 1 : 0); + ; } -void shaders_update(ShaderProgram program, FileArray fragment_shaders, +void shaders_update(ShaderProgram program, File fragment_shader, unsigned int i) { bool result; - result = compile_shader(program.fragment_shaders[i], - fragment_shaders.values[i].path, - fragment_shaders.values[i].content); + result = compile_shader(program.fragment_shaders[i], fragment_shader.path, + fragment_shader.content); if (result) { glLinkProgram(program.programs[i]); diff --git a/src/shaders.h b/src/shaders.h index ad45181..fc2cb63 100644 --- a/src/shaders.h +++ b/src/shaders.h @@ -3,10 +3,11 @@ #ifndef SHADERS_H #define SHADERS_H -ShaderProgram shaders_init(Project project, SharedContext *context, - VideoCaptureArray inputs, ShaderProgram *previous); +void shaders_init(ShaderProgram *program, Project *project, + SharedContext *context, VideoCaptureArray inputs, + bool rebind); -void shaders_update(ShaderProgram program, FileArray fragment_shaders, +void shaders_update(ShaderProgram program, File fragment_shader, unsigned int i); void shaders_compute(ShaderProgram program, SharedContext *context, diff --git a/src/string.c b/src/string.c index 1f93662..7cf2822 100644 --- a/src/string.c +++ b/src/string.c @@ -4,17 +4,6 @@ #include "string.h" -char *string_concat(const char *s1, const char *s2) { - char *result; - - result = malloc(strlen(s1) + strlen(s2) + 1); // +1 for the null-terminator - - strcpy(result, s1); - strcat(result, s2); - - return result; -} - unsigned int string_trim(char *str) { // https://www.delftstack.com/howto/c/trim-string-in-c/ unsigned int start; @@ -61,4 +50,22 @@ bool string_is_number(char *value) { } } return true; +} + +char *string_replace_at(char *src, unsigned int from, unsigned int to, + char *rpl) { + unsigned long src_len, rpl_len; + char *dst; + + src_len = strlen(src); + rpl_len = strlen(rpl); + + dst = + malloc(src_len - (to - from) + rpl_len + 1); // +1 for the null-terminator + + strncpy(dst, src, from); + strncpy(dst + from, rpl, rpl_len); + strncpy(dst + from + rpl_len, src + to, src_len - to); + + return dst; } \ No newline at end of file diff --git a/src/string.h b/src/string.h index 07c96ae..8734b1d 100644 --- a/src/string.h +++ b/src/string.h @@ -3,10 +3,11 @@ #ifndef STRINGS_H #define STRINGS_H -char *string_concat(const char *s1, const char *s2); - unsigned int string_trim(char *str); bool string_is_number(char *value); +char *string_replace_at(char *src, unsigned int from, unsigned int to, + char *rpl); + #endif /* STRINGS_H */ \ No newline at end of file diff --git a/src/types.h b/src/types.h index 991dc30..eb85a6a 100644 --- a/src/types.h +++ b/src/types.h @@ -242,12 +242,13 @@ typedef struct MidiDevice { typedef struct Project { bool error; + char path[STR_LEN]; ConfigFile config; StateConfig state_config; - FileArray fragment_shaders; - File common_shader_code; // TODO change unsigned int frag_count; unsigned int in_count; + UintArray sub_counts; + File fragment_shaders[MAX_FRAG][MAX_SUB_FILE + 1]; } Project; #endif /* TYPES_H */ \ No newline at end of file