]> git.sesse.net Git - nageru/commitdiff
Embed shaders into the binary.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 13 Nov 2018 23:27:44 +0000 (00:27 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 25 Nov 2018 22:36:26 +0000 (23:36 +0100)
bin2h.cpp [new file with mode: 0644]
chroma_subsampler.cpp
embedded_files.h [new file with mode: 0644]
flow.cpp
meson.build

diff --git a/bin2h.cpp b/bin2h.cpp
new file mode 100644 (file)
index 0000000..a396afe
--- /dev/null
+++ b/bin2h.cpp
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <string>
+
+using namespace std;
+
+int main(int argc, char **argv)
+{
+       if (argc != 4) {
+               fprintf(stderr, "Usage: bin2h INFILE BASENAME OUTFILE\n");
+               return 1;
+       }
+
+       string basename = argv[2];
+       for (char &ch : basename) {
+               if (!isalpha(ch) && !isdigit(ch)) {
+                       ch = '_';
+               }
+       }
+
+       FILE *infp = fopen(argv[1], "rb");
+       if (infp == nullptr) {
+               perror(argv[1]);
+               exit(1);
+       }
+
+       FILE *outfp = fopen(argv[3], "w");
+       if (outfp == nullptr) {
+               perror(argv[3]);
+               exit(1);
+       }
+
+       fprintf(outfp, "// Generated by bin2h.cpp from %s. Do not edit by hand.\n", argv[1]);
+       fprintf(outfp, "#include <stddef.h>\n");
+       fprintf(outfp, "unsigned char _binary_%s[] = {", basename.c_str());
+
+       size_t num_bytes = 0;
+       while (!feof(infp)) {
+               if (num_bytes++ % 16 == 0) {
+                       fprintf(outfp, "\n\t");
+               }
+               int ch = getc(infp);
+               if (ch == -1) {
+                       break;
+               }
+               fprintf(outfp, "0x%02x, ", ch);
+       }
+       fprintf(outfp, "\n};\n");
+       fprintf(outfp, "unsigned char *_binary_%s_data = _binary_%s;\n", basename.c_str(), basename.c_str());
+       fprintf(outfp, "size_t _binary_%s_size = sizeof(_binary_%s);\n", basename.c_str(), basename.c_str());
+       return 0;
+}
index 04fd5945f0f3b80ae3fc1c5edc0201f432377283..d064bc7c1d821d29bf9de86fc0194de0cfddba78 100644 (file)
@@ -3,11 +3,13 @@
 #include <movit/util.h>
 #include <string>
 
+#include "embedded_files.h"
+
 #define BUFFER_OFFSET(i) ((char *)nullptr + (i))
 
 using namespace std;
 
-string read_file(const string &filename);
+string read_file(const string &filename, const unsigned char *start = nullptr, const size_t size = 0);
 GLuint compile_shader(const string &shader_src, GLenum type);
 GLuint link_program(GLuint vs_obj, GLuint fs_obj);
 void bind_sampler(GLuint program, GLint location, GLuint texture_unit, GLuint tex, GLuint sampler);
@@ -69,8 +71,8 @@ ChromaSubsampler::ChromaSubsampler()
        //
        // See also http://www.poynton.com/PDFs/Merging_RGB_and_422.pdf, pages 6–7.
 
-       cbcr_vs_obj = compile_shader(read_file("chroma_subsample.vert"), GL_VERTEX_SHADER);
-       cbcr_fs_obj = compile_shader(read_file("chroma_subsample.frag"), GL_FRAGMENT_SHADER);
+       cbcr_vs_obj = compile_shader(read_file("chroma_subsample.vert", _binary_chroma_subsample_vert_data, _binary_chroma_subsample_vert_size), GL_VERTEX_SHADER);
+       cbcr_fs_obj = compile_shader(read_file("chroma_subsample.frag", _binary_chroma_subsample_frag_data, _binary_chroma_subsample_frag_size), GL_FRAGMENT_SHADER);
        cbcr_program = link_program(cbcr_vs_obj, cbcr_fs_obj);
 
        // Set up the VAO containing all the required position data.
diff --git a/embedded_files.h b/embedded_files.h
new file mode 100644 (file)
index 0000000..83cf0fc
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef _EMBEDDED_FILES_H
+#define _EMBEDDED_FILES_H 1
+
+// Files that are embedded into the binary as part of the build process.
+// They are used as a backup if the files are not available on disk
+// (which is typically the case if the program is installed, as opposed to
+// being run during development).
+
+#include <stddef.h>
+
+extern const unsigned char *_binary_add_base_flow_frag_data;
+extern const size_t _binary_add_base_flow_frag_size;
+extern const unsigned char *_binary_blend_frag_data;
+extern const size_t _binary_blend_frag_size;
+extern const unsigned char *_binary_chroma_subsample_frag_data;
+extern const size_t _binary_chroma_subsample_frag_size;
+extern const unsigned char *_binary_chroma_subsample_vert_data;
+extern const size_t _binary_chroma_subsample_vert_size;
+extern const unsigned char *_binary_densify_frag_data;
+extern const size_t _binary_densify_frag_size;
+extern const unsigned char *_binary_densify_vert_data;
+extern const size_t _binary_densify_vert_size;
+extern const unsigned char *_binary_derivatives_frag_data;
+extern const size_t _binary_derivatives_frag_size;
+extern const unsigned char *_binary_diffusivity_frag_data;
+extern const size_t _binary_diffusivity_frag_size;
+extern const unsigned char *_binary_equations_frag_data;
+extern const size_t _binary_equations_frag_size;
+extern const unsigned char *_binary_equations_vert_data;
+extern const size_t _binary_equations_vert_size;
+extern const unsigned char *_binary_gray_frag_data;
+extern const size_t _binary_gray_frag_size;
+extern const unsigned char *_binary_hole_blend_frag_data;
+extern const size_t _binary_hole_blend_frag_size;
+extern const unsigned char *_binary_hole_fill_frag_data;
+extern const size_t _binary_hole_fill_frag_size;
+extern const unsigned char *_binary_hole_fill_vert_data;
+extern const size_t _binary_hole_fill_vert_size;
+extern const unsigned char *_binary_motion_search_frag_data;
+extern const size_t _binary_motion_search_frag_size;
+extern const unsigned char *_binary_motion_search_vert_data;
+extern const size_t _binary_motion_search_vert_size;
+extern const unsigned char *_binary_prewarp_frag_data;
+extern const size_t _binary_prewarp_frag_size;
+extern const unsigned char *_binary_resize_flow_frag_data;
+extern const size_t _binary_resize_flow_frag_size;
+extern const unsigned char *_binary_sobel_frag_data;
+extern const size_t _binary_sobel_frag_size;
+extern const unsigned char *_binary_sor_frag_data;
+extern const size_t _binary_sor_frag_size;
+extern const unsigned char *_binary_sor_vert_data;
+extern const size_t _binary_sor_vert_size;
+extern const unsigned char *_binary_splat_frag_data;
+extern const size_t _binary_splat_frag_size;
+extern const unsigned char *_binary_splat_vert_data;
+extern const size_t _binary_splat_vert_size;
+extern const unsigned char *_binary_vs_vert_data;
+extern const size_t _binary_vs_vert_size;
+
+#endif  // !defined(_EMBEDDED_FILES_H)
index e672fe6f6d8fefb8ecc97935b73af79ee3495f8e..5125d26b53a5d2f09bde21132136d2a6ec333b5c 100644 (file)
--- a/flow.cpp
+++ b/flow.cpp
@@ -2,12 +2,14 @@
 
 #include "flow.h"
 
+#include "embedded_files.h"
 #include "gpu_timers.h"
 #include "util.h"
 
 #include <algorithm>
 #include <assert.h>
 #include <deque>
+#include <dlfcn.h>
 #include <epoxy/gl.h>
 #include <map>
 #include <memory>
@@ -48,10 +50,18 @@ int find_num_levels(int width, int height)
        return levels;
 }
 
-string read_file(const string &filename)
+string read_file(const string &filename, const unsigned char *start = nullptr, const size_t size = 0)
 {
        FILE *fp = fopen(filename.c_str(), "r");
        if (fp == nullptr) {
+               // Fall back to the version we compiled in. (We prefer disk if we can,
+               // since that makes it possible to work on shaders without recompiling
+               // all the time.)
+               if (start != nullptr) {
+                       return string(reinterpret_cast<const char *>(start),
+                               reinterpret_cast<const char *>(start) + size);
+               }
+
                perror(filename.c_str());
                exit(1);
        }
@@ -62,7 +72,7 @@ string read_file(const string &filename)
                exit(1);
        }
 
-       int size = ftell(fp);
+       int disk_size = ftell(fp);
 
        ret = fseek(fp, 0, SEEK_SET);
        if (ret == -1) {
@@ -71,15 +81,15 @@ string read_file(const string &filename)
        }
 
        string str;
-       str.resize(size);
-       ret = fread(&str[0], size, 1, fp);
+       str.resize(disk_size);
+       ret = fread(&str[0], disk_size, 1, fp);
        if (ret == -1) {
                perror("fread");
                exit(1);
        }
        if (ret == 0) {
                fprintf(stderr, "Short read when trying to read %d bytes from %s\n",
-                       size, filename.c_str());
+                       disk_size, filename.c_str());
                exit(1);
        }
        fclose(fp);
@@ -202,8 +212,8 @@ void PersistentFBOSetWithDepth<num_elements>::render_to(GLuint depth_rb, const a
 
 GrayscaleConversion::GrayscaleConversion()
 {
-       gray_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       gray_fs_obj = compile_shader(read_file("gray.frag"), GL_FRAGMENT_SHADER);
+       gray_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
+       gray_fs_obj = compile_shader(read_file("gray.frag", _binary_gray_frag_data, _binary_gray_frag_size), GL_FRAGMENT_SHADER);
        gray_program = link_program(gray_vs_obj, gray_fs_obj);
 
        // Set up the VAO containing all the required position/texcoord data.
@@ -231,8 +241,8 @@ void GrayscaleConversion::exec(GLint tex, GLint gray_tex, int width, int height,
 
 Sobel::Sobel()
 {
-       sobel_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       sobel_fs_obj = compile_shader(read_file("sobel.frag"), GL_FRAGMENT_SHADER);
+       sobel_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
+       sobel_fs_obj = compile_shader(read_file("sobel.frag", _binary_sobel_frag_data, _binary_sobel_frag_size), GL_FRAGMENT_SHADER);
        sobel_program = link_program(sobel_vs_obj, sobel_fs_obj);
 
        uniform_tex = glGetUniformLocation(sobel_program, "tex");
@@ -252,8 +262,8 @@ void Sobel::exec(GLint tex_view, GLint grad_tex, int level_width, int level_heig
 MotionSearch::MotionSearch(const OperatingPoint &op)
        : op(op)
 {
-       motion_vs_obj = compile_shader(read_file("motion_search.vert"), GL_VERTEX_SHADER);
-       motion_fs_obj = compile_shader(read_file("motion_search.frag"), GL_FRAGMENT_SHADER);
+       motion_vs_obj = compile_shader(read_file("motion_search.vert", _binary_motion_search_vert_data, _binary_motion_search_vert_size), GL_VERTEX_SHADER);
+       motion_fs_obj = compile_shader(read_file("motion_search.frag", _binary_motion_search_frag_data, _binary_motion_search_frag_size), GL_FRAGMENT_SHADER);
        motion_search_program = link_program(motion_vs_obj, motion_fs_obj);
 
        uniform_inv_image_size = glGetUniformLocation(motion_search_program, "inv_image_size");
@@ -288,8 +298,8 @@ void MotionSearch::exec(GLuint tex_view, GLuint grad_tex, GLuint flow_tex, GLuin
 Densify::Densify(const OperatingPoint &op)
        : op(op)
 {
-       densify_vs_obj = compile_shader(read_file("densify.vert"), GL_VERTEX_SHADER);
-       densify_fs_obj = compile_shader(read_file("densify.frag"), GL_FRAGMENT_SHADER);
+       densify_vs_obj = compile_shader(read_file("densify.vert", _binary_densify_vert_data, _binary_densify_vert_size), GL_VERTEX_SHADER);
+       densify_fs_obj = compile_shader(read_file("densify.frag", _binary_densify_frag_data, _binary_densify_frag_size), GL_FRAGMENT_SHADER);
        densify_program = link_program(densify_vs_obj, densify_fs_obj);
 
        uniform_patch_size = glGetUniformLocation(densify_program, "patch_size");
@@ -319,8 +329,8 @@ void Densify::exec(GLuint tex_view, GLuint flow_tex, GLuint dense_flow_tex, int
 
 Prewarp::Prewarp()
 {
-       prewarp_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       prewarp_fs_obj = compile_shader(read_file("prewarp.frag"), GL_FRAGMENT_SHADER);
+       prewarp_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
+       prewarp_fs_obj = compile_shader(read_file("prewarp.frag", _binary_prewarp_frag_data, _binary_prewarp_frag_size), GL_FRAGMENT_SHADER);
        prewarp_program = link_program(prewarp_vs_obj, prewarp_fs_obj);
 
        uniform_image_tex = glGetUniformLocation(prewarp_program, "image_tex");
@@ -342,8 +352,8 @@ void Prewarp::exec(GLuint tex_view, GLuint flow_tex, GLuint I_tex, GLuint I_t_te
 
 Derivatives::Derivatives()
 {
-       derivatives_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       derivatives_fs_obj = compile_shader(read_file("derivatives.frag"), GL_FRAGMENT_SHADER);
+       derivatives_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
+       derivatives_fs_obj = compile_shader(read_file("derivatives.frag", _binary_derivatives_frag_data, _binary_derivatives_frag_size), GL_FRAGMENT_SHADER);
        derivatives_program = link_program(derivatives_vs_obj, derivatives_fs_obj);
 
        uniform_tex = glGetUniformLocation(derivatives_program, "tex");
@@ -363,8 +373,8 @@ void Derivatives::exec(GLuint input_tex, GLuint I_x_y_tex, GLuint beta_0_tex, in
 
 ComputeDiffusivity::ComputeDiffusivity()
 {
-       diffusivity_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       diffusivity_fs_obj = compile_shader(read_file("diffusivity.frag"), GL_FRAGMENT_SHADER);
+       diffusivity_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
+       diffusivity_fs_obj = compile_shader(read_file("diffusivity.frag", _binary_diffusivity_frag_data, _binary_diffusivity_frag_size), GL_FRAGMENT_SHADER);
        diffusivity_program = link_program(diffusivity_vs_obj, diffusivity_fs_obj);
 
        uniform_flow_tex = glGetUniformLocation(diffusivity_program, "flow_tex");
@@ -391,8 +401,8 @@ void ComputeDiffusivity::exec(GLuint flow_tex, GLuint diff_flow_tex, GLuint diff
 
 SetupEquations::SetupEquations()
 {
-       equations_vs_obj = compile_shader(read_file("equations.vert"), GL_VERTEX_SHADER);
-       equations_fs_obj = compile_shader(read_file("equations.frag"), GL_FRAGMENT_SHADER);
+       equations_vs_obj = compile_shader(read_file("equations.vert", _binary_equations_vert_data, _binary_equations_vert_size), GL_VERTEX_SHADER);
+       equations_fs_obj = compile_shader(read_file("equations.frag", _binary_equations_frag_data, _binary_equations_frag_size), GL_FRAGMENT_SHADER);
        equations_program = link_program(equations_vs_obj, equations_fs_obj);
 
        uniform_I_x_y_tex = glGetUniformLocation(equations_program, "I_x_y_tex");
@@ -428,8 +438,8 @@ void SetupEquations::exec(GLuint I_x_y_tex, GLuint I_t_tex, GLuint diff_flow_tex
 
 SOR::SOR()
 {
-       sor_vs_obj = compile_shader(read_file("sor.vert"), GL_VERTEX_SHADER);
-       sor_fs_obj = compile_shader(read_file("sor.frag"), GL_FRAGMENT_SHADER);
+       sor_vs_obj = compile_shader(read_file("sor.vert", _binary_sor_vert_data, _binary_sor_vert_size), GL_VERTEX_SHADER);
+       sor_fs_obj = compile_shader(read_file("sor.frag", _binary_sor_frag_data, _binary_sor_frag_size), GL_FRAGMENT_SHADER);
        sor_program = link_program(sor_vs_obj, sor_fs_obj);
 
        uniform_diff_flow_tex = glGetUniformLocation(sor_program, "diff_flow_tex");
@@ -490,8 +500,8 @@ void SOR::exec(GLuint diff_flow_tex, GLuint equation_red_tex, GLuint equation_bl
 
 AddBaseFlow::AddBaseFlow()
 {
-       add_flow_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       add_flow_fs_obj = compile_shader(read_file("add_base_flow.frag"), GL_FRAGMENT_SHADER);
+       add_flow_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
+       add_flow_fs_obj = compile_shader(read_file("add_base_flow.frag", _binary_add_base_flow_frag_data, _binary_add_base_flow_frag_size), GL_FRAGMENT_SHADER);
        add_flow_program = link_program(add_flow_vs_obj, add_flow_fs_obj);
 
        uniform_diff_flow_tex = glGetUniformLocation(add_flow_program, "diff_flow_tex");
@@ -513,8 +523,8 @@ void AddBaseFlow::exec(GLuint base_flow_tex, GLuint diff_flow_tex, int level_wid
 
 ResizeFlow::ResizeFlow()
 {
-       resize_flow_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
-       resize_flow_fs_obj = compile_shader(read_file("resize_flow.frag"), GL_FRAGMENT_SHADER);
+       resize_flow_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
+       resize_flow_fs_obj = compile_shader(read_file("resize_flow.frag", _binary_resize_flow_frag_data, _binary_resize_flow_frag_size), GL_FRAGMENT_SHADER);
        resize_flow_program = link_program(resize_flow_vs_obj, resize_flow_fs_obj);
 
        uniform_flow_tex = glGetUniformLocation(resize_flow_program, "flow_tex");
@@ -770,8 +780,8 @@ GLuint DISComputeFlow::exec(GLuint tex, FlowDirection flow_direction, ResizeStra
 Splat::Splat(const OperatingPoint &op)
        : op(op)
 {
-       splat_vs_obj = compile_shader(read_file("splat.vert"), GL_VERTEX_SHADER);
-       splat_fs_obj = compile_shader(read_file("splat.frag"), GL_FRAGMENT_SHADER);
+       splat_vs_obj = compile_shader(read_file("splat.vert", _binary_splat_vert_data, _binary_splat_vert_size), GL_VERTEX_SHADER);
+       splat_fs_obj = compile_shader(read_file("splat.frag", _binary_splat_frag_data, _binary_splat_frag_size), GL_FRAGMENT_SHADER);
        splat_program = link_program(splat_vs_obj, splat_fs_obj);
 
        uniform_splat_size = glGetUniformLocation(splat_program, "splat_size");
@@ -813,8 +823,8 @@ void Splat::exec(GLuint gray_tex, GLuint bidirectional_flow_tex, GLuint flow_tex
 
 HoleFill::HoleFill()
 {
-       fill_vs_obj = compile_shader(read_file("hole_fill.vert"), GL_VERTEX_SHADER);
-       fill_fs_obj = compile_shader(read_file("hole_fill.frag"), GL_FRAGMENT_SHADER);
+       fill_vs_obj = compile_shader(read_file("hole_fill.vert", _binary_hole_fill_vert_data, _binary_hole_fill_vert_size), GL_VERTEX_SHADER);
+       fill_fs_obj = compile_shader(read_file("hole_fill.frag", _binary_hole_fill_frag_data, _binary_hole_fill_frag_size), GL_FRAGMENT_SHADER);
        fill_program = link_program(fill_vs_obj, fill_fs_obj);
 
        uniform_tex = glGetUniformLocation(fill_program, "tex");
@@ -877,8 +887,8 @@ void HoleFill::exec(GLuint flow_tex, GLuint depth_rb, GLuint temp_tex[3], int wi
 
 HoleBlend::HoleBlend()
 {
-       blend_vs_obj = compile_shader(read_file("hole_fill.vert"), GL_VERTEX_SHADER);  // Reuse the vertex shader from the fill.
-       blend_fs_obj = compile_shader(read_file("hole_blend.frag"), GL_FRAGMENT_SHADER);
+       blend_vs_obj = compile_shader(read_file("hole_fill.vert", _binary_hole_fill_vert_data, _binary_hole_fill_vert_size), GL_VERTEX_SHADER);  // Reuse the vertex shader from the fill.
+       blend_fs_obj = compile_shader(read_file("hole_blend.frag", _binary_hole_blend_frag_data, _binary_hole_blend_frag_size), GL_FRAGMENT_SHADER);
        blend_program = link_program(blend_vs_obj, blend_fs_obj);
 
        uniform_left_tex = glGetUniformLocation(blend_program, "left_tex");
@@ -916,7 +926,7 @@ void HoleBlend::exec(GLuint flow_tex, GLuint depth_rb, GLuint temp_tex[3], int w
 Blend::Blend(bool split_ycbcr_output)
        : split_ycbcr_output(split_ycbcr_output)
 {
-       string frag_shader = read_file("blend.frag");
+       string frag_shader = read_file("blend.frag", _binary_blend_frag_data, _binary_blend_frag_size);
        if (split_ycbcr_output) {
                // Insert after the first #version line.
                size_t offset = frag_shader.find('\n');
@@ -924,7 +934,7 @@ Blend::Blend(bool split_ycbcr_output)
                frag_shader = frag_shader.substr(0, offset + 1) + "#define SPLIT_YCBCR_OUTPUT 1\n" + frag_shader.substr(offset + 1);
        }
 
-       blend_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER);
+       blend_vs_obj = compile_shader(read_file("vs.vert", _binary_vs_vert_data, _binary_vs_vert_size), GL_VERTEX_SHADER);
        blend_fs_obj = compile_shader(frag_shader, GL_FRAGMENT_SHADER);
        blend_program = link_program(blend_vs_obj, blend_fs_obj);
 
index 8308335fa6ee127dc339595722cf03cc66825f63..18f9272619bfcaa2bf77faa95e69c196db581648 100644 (file)
@@ -48,7 +48,24 @@ srcs += ['mainwindow.cpp', 'jpeg_frame_view.cpp', 'clip_list.cpp']
 srcs += moc_files
 srcs += proto_generated
 
+# Shaders needed at runtime.
+shaders = ['chroma_subsample.vert', 'densify.vert', 'equations.vert', 'hole_fill.vert', 'motion_search.vert', 'sor.vert', 'splat.vert', 'vs.vert']
+shaders += ['add_base_flow.frag', 'blend.frag', 'chroma_subsample.frag', 'densify.frag', 'derivatives.frag', 'diffusivity.frag',
+  'equations.frag', 'gray.frag', 'hole_blend.frag', 'hole_fill.frag', 'motion_search.frag', 'prewarp.frag', 'resize_flow.frag',
+  'sobel.frag', 'sor.frag', 'splat.frag']
+
+foreach shader : shaders
+  run_command('ln', '-s', join_paths(meson.current_source_dir(), shader), meson.current_build_dir())
+endforeach
+
+bin2h = executable('bin2h', 'bin2h.cpp')
+bin2h_gen = generator(bin2h, \
+  output    : ['@PLAINNAME@.cpp'],
+  arguments : ['@INPUT@', '@PLAINNAME@', '@OUTPUT@'])
+shader_srcs = bin2h_gen.process(shaders)
+srcs += shader_srcs
+
 executable('futatabi', srcs, dependencies: [qt5deps, libjpegdep, movitdep, libmicrohttpddep, protobufdep, sqlite3dep, vax11dep, vadrmdep, x11dep, libavformatdep, libavcodecdep, libavutildep, libswscaledep])
-executable('flow', 'flow_main.cpp', 'flow.cpp', 'gpu_timers.cpp', dependencies: [epoxydep, sdl2dep, sdl2_imagedep])
+executable('flow', 'flow_main.cpp', 'flow.cpp', 'gpu_timers.cpp', shader_srcs, dependencies: [epoxydep, sdl2dep, sdl2_imagedep])
 executable('eval', 'eval.cpp', 'util.cpp')
 executable('vis', 'vis.cpp', 'util.cpp')