feat: working include directive

This commit is contained in:
2025-11-08 18:24:52 +01:00
parent de5fc8c641
commit 9c60d5dc4f
25 changed files with 248 additions and 154 deletions
+1 -1
View File
@@ -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
+4 -1
View File
@@ -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
+5
View File
@@ -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 \
+7 -2
View File
@@ -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
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// VIDEO 1
// -----------
// IN: 1 (RAW IN A)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// MONITOR
// ---
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// VIDEO 2
// -----------
// IN: 2 (RAW IN B)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// SRC A
// -----------
// OUT: 5 (FX A)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// SRC B
// -----------
// OUT: 6 (FX B)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// FX A
// -------------
// IN: 5 (SRC A)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// FX B
// -------------
// IN: 6 (SRC B)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// A+B
// ------------
// IN: 7 (FX A)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// MFX
// ------------
// IN: 9 (A+B)
+2
View File
@@ -1,3 +1,5 @@
#include frag0.glsl
// OUT
// ---
+5 -1
View File
@@ -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
+20 -12
View File
@@ -2,6 +2,7 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
@@ -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); }
void file_free_array(FileArray *files) {
unsigned int i;
for (i = 0; i < files->length; i++) {
file_free(&files->values[i]);
}
}
+3 -3
View File
@@ -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 */
+9 -6
View File
@@ -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();
}
+107 -60
View File
@@ -1,95 +1,142 @@
#include <string.h>
#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);
}
+2 -2
View File
@@ -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 */
+37 -48
View File
@@ -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]);
+4 -3
View File
@@ -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,
+18 -11
View File
@@ -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;
}
+3 -2
View File
@@ -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 */
+3 -2
View File
@@ -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 */