├── .gitignore ├── ISSUES.md ├── Makefile ├── NOTES.md ├── README.md ├── Vars.make ├── c ├── Rules.make ├── libshade │ ├── Makefile │ ├── bcm.c │ ├── bcm.h │ ├── egl.c │ ├── egl.h │ ├── exec.c │ ├── exec.h │ ├── leds.c │ ├── leds.h │ ├── mpsse.c │ ├── mpsse.h │ ├── prog.c │ ├── prog.h │ ├── queue.c │ ├── queue.h │ ├── render.c │ ├── render.h │ ├── shade.c │ └── shade.h └── test │ ├── Makefile │ ├── ltest.c │ └── ptest.c ├── python ├── Makefile ├── pytest ├── setup.py ├── shade │ └── __init__.py ├── shaderbox └── test.png ├── scripts ├── conic ├── nyan ├── nyan.png ├── plasma ├── smiley └── smiley.png ├── shaders ├── alphabet.png ├── alphabet6.png ├── block.glsl ├── conic.glsl ├── curve-wobbler.glsl ├── filling-tiles-3D.glsl ├── mb.glsl ├── munch0.glsl ├── munch1.glsl ├── rgb.glsl ├── smiley.glsl ├── smiley.png ├── smooth-voronoi-contours.glsl ├── v.glsl └── voronoi.glsl └── test.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | lib*.a 4 | lib*.so 5 | a.out 6 | ltest-dynamic 7 | ltest-static 8 | ptest 9 | -------------------------------------------------------------------------------- /ISSUES.md: -------------------------------------------------------------------------------- 1 | ## RESOLVED: Naming 2 | 3 | `pyshade` is not good. Maybe `libshade` and `shaderbox`/`shaderflat` 4 | front ends. 5 | 6 | #### Resolution: 7 | Project is *shaderboy*. 8 | Executables will be **shaderbox** and eventually **shaderflat**. 9 | Library is **libshade**. 10 | C symbol prefix is `SHD_`/`shd_`. 11 | 12 | ## RESOLVED: info log 13 | 14 | Should not be part of prog object, should not force non-const for 15 | `instantiate` and `is_okay`. 16 | 17 | #### Resolution: 18 | `prog_is_okay` now has an `info_log` out parameter. 19 | `prog` type is now const in `exec` and `render` modules. 20 | 21 | ## RESOLVED: Cleanup 22 | 23 | `shd_deinit` does not return. 24 | 25 | #### Resolution: 26 | Stopped using `pthread_cancel`; modified `check_cancel` to detect 27 | shutdown and exit thread. 28 | 29 | ## RESOLVED: Need GIT. 30 | 31 | Need git. Depends on naming. 32 | 33 | #### Resolution: 34 | Has git. https://github.com/kbob/shaderboy 35 | 36 | ## BCM cleanup 37 | 38 | `bcm_context` vs `vidocore_context` is silly. 39 | 40 | ## RESOLVED: Implement Textures 41 | 42 | #### Resolution: 43 | Implemented textures. 44 | 45 | ## RESOLVED: Implement Noise 46 | 47 | #### Resolution: 48 | Implemented noise. 49 | 50 | ## RESOLVED: Implement Time 51 | 52 | Should implement at least one time variable. 53 | 54 | #### Resolution: 55 | Implemented `iTime` and `iResolution`. 56 | 57 | ## Implement iTimeDelta. 58 | 59 | ## RESOLVED: Implement iFrame. 60 | 61 | ####Resolution: 62 | Implemented `iFrame`. 63 | 64 | ## Implement iDate. 65 | 66 | ## Implement dummy iMouse. 67 | 68 | ## Implement CPU and IRQ affinity 69 | 70 | ## Get real-time kernel 71 | 72 | ## `shd_deinit` should destroy all programs. 73 | 74 | ## LEDs size is hardcoded in `render.update_predefines`. 75 | 76 | ## RESOLVED: Implement `shd_fps`. 77 | 78 | ####Resolution: 79 | Implemented `shd_fps`. 80 | 81 | ## Locks up if run duration less than 0. 82 | 83 | 84 | ## Implement iChannelResolution[] 85 | If the identifier is in the source, then `shaderbox` should insert the 86 | declaration and add it as a predefined. If `libshade` sees it as a 87 | predefined, then it should search its image table for images named 88 | `iChannel[0-3]` and set the corresponding uniform entries. Unused 89 | channels should have dimension 0x0, compatible with Shadertoy. 90 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include Vars.make 2 | 3 | ACTIONS := build test clean install uninstall 4 | SUBDIRS := c/libshade c/test python 5 | SUBDIR_ACTIONS := $(foreach A, $(ACTIONS), $(foreach S, $(SUBDIRS), $A-$S)) 6 | 7 | 8 | $(ACTIONS): %: $(foreach S, $(SUBDIRS), %-$S) 9 | 10 | $(SUBDIR_ACTIONS): T=$(subst -, ,$@) 11 | $(SUBDIR_ACTIONS): 12 | $(MAKE) -C $(lastword $T) $(firstword $T) 13 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Interface 2 | 3 | Module 4 | init 5 | finit 6 | Executor 7 | start 8 | stop 9 | fps 10 | use_program 11 | Program 12 | create 13 | destroy 14 | is_ok 15 | info_log 16 | attach_shader(type, source) 17 | attach_uniform(name, type, value) 18 | types: 2Dx4, 3Dx4 19 | attach_program(name, program) 20 | 21 | # Structure 22 | 23 | libshade.c 24 | shader.c 25 | program.c 26 | exec.c -- ??? 27 | 28 | # Threads 29 | 30 | python thread 31 | GL thread 32 | mpsse thread 33 | SPI thread 34 | 35 | each thread pair requires two buffers, a cond for the buffers, and 36 | a cond for the result. 37 | 38 | 39 | 40 | void producer() 41 | { 42 | while (1) { 43 | p = buffers[i]; 44 | fill(p); 45 | lock(); 46 | while (rdy == i)) { 47 | cond_wait(nonfull)) 48 | i = (i + 1) % 2; 49 | } 50 | 51 | } 52 | 53 | void consumer() 54 | { 55 | } 56 | 57 | # GL Textures 58 | 59 | glGenTextures => texture ID 60 | glActiveTexture - selects one of the texture units 61 | glBindTexture(GL_TEXTURE_2D, t_ID) - bind to "target" -- 1 per type? 62 | glTexImage2D(...) - attach image data to texture. 63 | glGenerateMipmap() - applies to most recent texture? 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shaderboy 2 | 3 | Shaderboy is an application and library to render OpenGL shaders 4 | on LED panels and cubes. It is optimized for and coupled to 5 | the Raspberry Pi and the iCEBreaker FPGA. 6 | 7 | Shaderboy draws inspiration from [Shadertoy](https://shadertoy.com) 8 | and [Shady](https://github.com/polyfloyd/shady). 9 | 10 | 11 | # Installation 12 | 13 | ```sh 14 | $ make && sudo make install 15 | ``` 16 | 17 | # Where's the Eye Candy? 18 | 19 | Most of the demos are in the `shaders` directory. 20 | 21 | ```sh 22 | cd shaders 23 | for demo in *.glsl 24 | do 25 | shaderbox --duration=10 $demo 26 | done 27 | ``` 28 | 29 | Some additional demos are in the `scripts` directory. 30 | 31 | 32 | # Architecture 33 | 34 | Shaderboy has several public APIs: Linux CLI, Python, and C. 35 | 36 | 37 | ## Command line 38 | 39 | At the highest level, the `shaderbox` command renders a fragment 40 | shader written in the OpenGL Shading Langues (GLSL) on the LEDs. 41 | 42 | ```sh 43 | $ echo 'void main() { gl_FragColor = vec4(1); }' | shaderbox 44 | ``` 45 | 46 | or 47 | 48 | ```sh 49 | $ shaderbox myfile.glsl 50 | ``` 51 | 52 | or, if you remember Unix, 53 | 54 | ```sh 55 | $ head -n 1 myfile.glsl 56 | #!/usr/bin/env shaderbox 57 | $ chmod +x myfile.glsl 58 | $ ./myfile.glsl 59 | ``` 60 | 61 | 62 | ## Python API 63 | 64 | You can also use Shaderboy from Python programs using the `shade` 65 | module. See the `scripts` subdirectory for examples. 66 | 67 | 68 | ## C API 69 | 70 | You can use Shaderboy from C programs using the `libshade` library. 71 | See `c/test/ltest.c` for an example. 72 | 73 | 74 | # GLSL extensions 75 | 76 | The Raspberry Pi uses GLSL 1.0. Shaderboy adds some extensions 77 | via a preprocessor. 78 | 79 | ## Predefined Variables 80 | 81 | To be documented. Similar to Shadertoy's. 82 | `iResolution`, `iTime`, `iFrame` are implemented. 83 | 84 | ## Alternate Entrypoints 85 | 86 | To be documented. Similar to Shady's. 87 | `main`, `mainImage`, `mainCube` are implemented. 88 | 89 | 90 | ## Pragmas 91 | 92 | To be documented. Similar to Shady's. 93 | ```c 94 | #pragma use [filename] 95 | #pragma map [variable]=image:[filename] 96 | #pragma map [variable]=builtin:[builtin] 97 | #pragma map [variable]=perip_map4:[device] 98 | ``` 99 | 100 | 101 | # Optimization 102 | 103 | Shaderboy creates three threads and does all rendering in those 104 | threads. Rendering is asynchronous with the calling thread. 105 | On a four core Raspberry Pi, one core is never blocked by 106 | rendering. 107 | 108 | * The **render** thread invokes the GPU to render pixels on screen. 109 | This uses OpenGL. 110 | 111 | * The **cmd** thread repackages pixels as commands for the FTDI chip 112 | on the iCEBreaker board. 113 | 114 | * The **output** thread sends commands to the iCEBreaker 115 | via the Raspberry Pi's USB interface. 116 | 117 | The render thread may be CPU or GPU bound. The output thread is 118 | usually waiting on the FTDI chip. And the cmd thread is there to 119 | offload and decouple the other two threads so they can always run. 120 | -------------------------------------------------------------------------------- /Vars.make: -------------------------------------------------------------------------------- 1 | INSTALL := install 2 | PREFIX := /usr/local 3 | PYTHON := $(shell which python3) 4 | -------------------------------------------------------------------------------- /c/Rules.make: -------------------------------------------------------------------------------- 1 | CPPFLAGS := 2 | CFLAGS := -g -Wall -Werror -fpic -Wmissing-prototypes 3 | LDFLAGS := -g -L/opt/vc/lib 4 | LDLIBS := -lbcm_host -lbrcmEGL -lbrcmGLESv2 -lftdi -lm -lpthread 5 | -------------------------------------------------------------------------------- /c/libshade/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Vars.make 2 | include ../Rules.make 3 | 4 | CPPFLAGS += -I/opt/vc/include 5 | LDFLAGS += -L/opt/vc/lib -fvisibility=hidden -Wl,-rpath=`pwd` 6 | LDLIBS += -lbcm_host -lbrcmEGL -lbrcmGLESv2 -lftdi -lm -lpthread 7 | 8 | libshade_CFILES := shade.c bcm.c egl.c exec.c leds.c mpsse.c prog.c \ 9 | queue.c render.c 10 | 11 | libshade_OFILES := $(libshade_CFILES:.c=.o) 12 | 13 | TARGETS := libshade.a libshade.so 14 | 15 | build: $(TARGETS) 16 | 17 | libshade.a: $(libshade_OFILES) 18 | $(AR) cr $@ $^ 19 | 20 | libshade.so: $(libshade_OFILES) 21 | $(LINK.o) -shared -o $@ $^ $(LDLIBS) 22 | 23 | test: build 24 | 25 | clean: 26 | rm -f *.o $(TARGETS) 27 | 28 | install: build 29 | $(INSTALL) libshade.a $(PREFIX)/lib 30 | $(INSTALL) libshade.so $(PREFIX)/lib 31 | ldconfig 32 | 33 | uninstall: 34 | rm -f $(PREFIX)/lib/libshade.a 35 | rm -f $(PREFIX)/lib//libshade.so 36 | -------------------------------------------------------------------------------- /c/libshade/bcm.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "bcm.h" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define VIEWPORT_WIDTH (6 * 64 * 2) 10 | #define VIEWPORT_HEIGHT (64 * 2) 11 | 12 | // #define FRAMEBUFFER_WIDTH (6 * 64) 13 | // #define FRAMEBUFFER_HEIGHT 64 14 | #define FRAMEBUFFER_WIDTH (800 / 2) // XXX 15 | #define FRAMEBUFFER_HEIGHT (600 / 2) // XXX 16 | 17 | // #define LED_WIDTH (6 * 64) 18 | // #define LED_HEIGHT 64 19 | 20 | static __thread char *last_error; 21 | 22 | typedef struct videocore_context { 23 | uint32_t surface_width; 24 | uint32_t surface_height; 25 | DISPMANX_DISPLAY_HANDLE_T display; 26 | DISPMANX_ELEMENT_HANDLE_T element; 27 | DISPMANX_RESOURCE_HANDLE_T screen_resource; 28 | } videocore_context; 29 | 30 | // Return NULL on success; return error message on failure. 31 | // Error message may contain "%m" to reference errno. 32 | static const char *init_videocore(videocore_context *ctx) 33 | { 34 | bcm_host_init(); 35 | 36 | uint32_t w, h; 37 | if (graphics_get_display_size(0, &w, &h) < 0) { 38 | return "can't get display 0"; 39 | } 40 | ctx->surface_width = w; 41 | ctx->surface_height = h; 42 | { // XXX XXX 43 | assert(w == 800 && h == 600); 44 | } 45 | 46 | ctx->display = vc_dispmanx_display_open(0); 47 | if (ctx->display == DISPMANX_NO_HANDLE) { 48 | return "vc_dispmanx_display_open failed"; 49 | } 50 | 51 | DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); 52 | if (update == DISPMANX_NO_HANDLE) { 53 | return "vc_dispmanx_update_start failed"; 54 | } 55 | 56 | // I don't know what this does. But it seems to work. 57 | const int32_t layer = 0; 58 | VC_RECT_T src_rect = { 59 | .x = 0, 60 | .y = 0, 61 | .width = VIEWPORT_WIDTH << 16, 62 | .height = VIEWPORT_HEIGHT << 16, 63 | }; 64 | VC_RECT_T dst_rect = { 65 | .x = 0, 66 | .y = h - VIEWPORT_HEIGHT, 67 | .width = VIEWPORT_WIDTH, 68 | .height = VIEWPORT_HEIGHT, 69 | }; 70 | VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_PREMULT, 0, 0 }; 71 | 72 | // printf("\n"); 73 | // printf("vc_dispmanx_element_add(\n"); 74 | // printf(" update=%#x,\n", update); 75 | // printf(" display=%#x,\n", ctx->display); 76 | // printf(" layer=%d,\n", layer); 77 | // printf(" dest_rect={%d, %d, %d, %d},\n", 78 | // dst_rect.x, dst_rect.y, dst_rect.width, dst_rect.height); 79 | // printf(" src=0,\n"); 80 | // printf(" src_rect={%d, %d, %#x, %#x},\n", 81 | // src_rect.x, src_rect.y, src_rect.width, src_rect.height); 82 | // printf(" protection=%d\n", DISPMANX_PROTECTION_NONE); 83 | // printf(" alpha={%d, 0, 0},\n", DISPMANX_FLAGS_ALPHA_PREMULT); 84 | // printf(" clamp=0,\n"); 85 | // printf(" transform=0);\n"); 86 | // printf("\n"); 87 | 88 | ctx->element = vc_dispmanx_element_add(update, 89 | ctx->display, 90 | layer, &dst_rect, 91 | 0, &src_rect, 92 | DISPMANX_PROTECTION_NONE, 93 | &alpha, 0, 0); 94 | if (ctx->element == DISPMANX_NO_HANDLE) { 95 | return "vc_dispmanx_element_add failed"; 96 | } 97 | 98 | if (vc_dispmanx_update_submit_sync(update) != 0) { 99 | return "vc_dispmanx_update_submit_sync failed"; 100 | } 101 | 102 | // from fbx2.c 103 | uint32_t native_image_handle = 0; 104 | ctx->screen_resource = 105 | vc_dispmanx_resource_create(VC_IMAGE_RGB565, 106 | FRAMEBUFFER_WIDTH, 107 | FRAMEBUFFER_HEIGHT, 108 | &native_image_handle); 109 | 110 | return NULL; 111 | } 112 | 113 | // Returns zero or positive on success. 114 | static int videocore_read_pixels(videocore_context *ctx, 115 | uint16_t pixel_buf[], 116 | size_t word_pitch) 117 | { 118 | 119 | static VC_RECT_T rect; 120 | vc_dispmanx_rect_set(&rect, 0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT); 121 | int r = vc_dispmanx_snapshot(ctx->display, ctx->screen_resource, 0); 122 | assert(r >= 0); // XXX 123 | if (r >= 0) { 124 | // static int been_here; 125 | // if (!been_here) { 126 | // been_here = 1; 127 | // printf("\n"); 128 | // printf("vc_dispmanx_resource_read_data(\n"); 129 | // printf(" handle=%#x,\n", ctx->screen_resource); 130 | // printf(" rect={%d, %d, %d, %d},\n", 131 | // rect.x, rect.y, rect.width, rect.height); 132 | // printf(" dst_address=%p,\n", pixel_buf); 133 | // printf(" dst_pitch=%u);\n", word_pitch * sizeof *pixel_buf); 134 | // printf("\n"); 135 | // } 136 | 137 | r = vc_dispmanx_resource_read_data(ctx->screen_resource, 138 | &rect, 139 | pixel_buf, 140 | word_pitch * sizeof *pixel_buf); 141 | } 142 | return r; 143 | } 144 | 145 | bcm_context init_bcm(void) 146 | { 147 | // Initialize VideoCore. 148 | videocore_context *vctx = calloc(1, sizeof *vctx); 149 | const char *err = init_videocore(vctx); 150 | if (err) { 151 | free(vctx); 152 | free(last_error); 153 | asprintf(&last_error, "init_videocore: %s\n", err); 154 | return NULL; 155 | } 156 | return (bcm_context)vctx; 157 | } 158 | 159 | void deinit_bcm(bcm_context bctx) 160 | { 161 | // assert(0 && "This function has not been tested."); 162 | videocore_context *vctx = bctx; 163 | vc_dispmanx_resource_delete(vctx->screen_resource); 164 | DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); 165 | if (update != DISPMANX_NO_HANDLE) { 166 | if (vc_dispmanx_element_remove(update, vctx->element) == 0) { 167 | (void)vc_dispmanx_update_submit_sync(update); 168 | } 169 | } 170 | (void)vc_dispmanx_display_close(vctx->display); 171 | free(vctx); 172 | } 173 | 174 | const char *bcm_last_error(void) 175 | { 176 | return last_error; 177 | } 178 | 179 | int bcm_get_surface_width(const bcm_context bctx) 180 | { 181 | return ((videocore_context *)bctx)->surface_width; 182 | } 183 | 184 | int bcm_get_surface_height(const bcm_context bctx) 185 | { 186 | return ((videocore_context *)bctx)->surface_height; 187 | } 188 | 189 | int bcm_get_framebuffer_width(const bcm_context bctx) 190 | { 191 | return FRAMEBUFFER_WIDTH; 192 | } 193 | 194 | int bcm_get_framebuffer_height(const bcm_context bctx) 195 | { 196 | return FRAMEBUFFER_HEIGHT; 197 | } 198 | 199 | int bcm_get_surface(const bcm_context bctx) 200 | { 201 | return ((videocore_context *)bctx)->element; 202 | } 203 | 204 | // returns zero on success 205 | int bcm_read_pixels(bcm_context bctx, 206 | uint16_t *pixels, 207 | uint16_t row_pitch) 208 | { 209 | videocore_context *vctx = bctx; 210 | return videocore_read_pixels(vctx, pixels, row_pitch); 211 | } 212 | -------------------------------------------------------------------------------- /c/libshade/bcm.h: -------------------------------------------------------------------------------- 1 | #ifndef BCM_included 2 | #define BCM_included 3 | 4 | #include 5 | 6 | typedef void *bcm_context; 7 | 8 | extern bcm_context init_bcm(void); 9 | extern void deinit_bcm(bcm_context); 10 | 11 | extern int bcm_get_surface_width(const bcm_context); 12 | extern int bcm_get_surface_height(const bcm_context); 13 | extern int bcm_get_framebuffer_width(const bcm_context); 14 | extern int bcm_get_framebuffer_height(const bcm_context); 15 | extern int bcm_get_surface(const bcm_context); 16 | 17 | // returns zero on success 18 | extern int bcm_read_pixels(bcm_context, 19 | uint16_t *pixels, 20 | uint16_t row_pitch); 21 | 22 | extern const char *bcm_last_error(void); 23 | 24 | #endif /* !BCM_included */ 25 | -------------------------------------------------------------------------------- /c/libshade/egl.c: -------------------------------------------------------------------------------- 1 | #include "egl.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | struct EGL_context { 8 | EGLDisplay display; 9 | EGLContext context; 10 | EGLSurface surface; 11 | int native_window[3]; 12 | }; 13 | 14 | static __thread char *last_error; 15 | static __thread char last_error_buf[256]; 16 | 17 | static char *egl_error_string(const char *function, EGLint err) 18 | { 19 | const char *reason; 20 | switch (err) { 21 | 22 | case EGL_NOT_INITIALIZED: 23 | reason = "not initialized"; 24 | break; 25 | 26 | case EGL_BAD_ALLOC: 27 | reason = "bad alloc"; 28 | break; 29 | 30 | case EGL_BAD_ATTRIBUTE: 31 | reason = "bad attribute"; 32 | break; 33 | 34 | case EGL_BAD_CONFIG: 35 | reason = "bad config"; 36 | break; 37 | 38 | case EGL_BAD_DISPLAY: 39 | reason = "bad display"; 40 | break; 41 | 42 | case EGL_BAD_MATCH: 43 | reason = "bad match"; 44 | break; 45 | 46 | case EGL_BAD_NATIVE_WINDOW: 47 | reason = "bad native window"; 48 | break; 49 | 50 | case EGL_BAD_PARAMETER: 51 | reason = "bad parameter"; 52 | break; 53 | 54 | default: 55 | reason = "unknown error"; 56 | break; 57 | } 58 | static __thread char buf[256]; 59 | snprintf(buf, sizeof buf, "%s: %s", function, reason); 60 | return buf; 61 | } 62 | 63 | const char *EGL_last_error(void) 64 | { 65 | return last_error; 66 | } 67 | 68 | EGL_context *init_EGL(uint32_t native_surface, 69 | uint32_t surface_width, 70 | uint32_t surface_height) 71 | { 72 | EGL_context *ctx = calloc(1, sizeof *ctx); 73 | 74 | ctx->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 75 | if (ctx->display == EGL_NO_DISPLAY) { 76 | strerror_r(errno, last_error_buf, sizeof last_error_buf); 77 | last_error = last_error_buf; 78 | free(ctx); 79 | return NULL; 80 | } 81 | 82 | EGLint major = 0, minor = 0; 83 | if (eglInitialize(ctx->display, &major, &minor) == EGL_FALSE) { 84 | last_error = egl_error_string("eglInitialize", eglGetError()); 85 | eglTerminate(ctx->display); 86 | free(ctx); 87 | return NULL; 88 | } 89 | // fprintf(stderr, "This is EGL %d.%d\n", major, minor); 90 | 91 | static const EGLint attribute_list[] = { 92 | EGL_RED_SIZE, 8, 93 | EGL_GREEN_SIZE, 8, 94 | EGL_BLUE_SIZE, 8, 95 | EGL_ALPHA_SIZE, 8, 96 | // EGL_SAMPLES, 4, 97 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 98 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 99 | EGL_NATIVE_RENDERABLE, EGL_TRUE, 100 | EGL_NONE 101 | }; 102 | EGLConfig config = 0; 103 | EGLint numconfig = 0; 104 | if (!eglChooseConfig(ctx->display, 105 | attribute_list, 106 | &config, 1, 107 | &numconfig)) { 108 | last_error = egl_error_string("eglChooseConfig", eglGetError()); 109 | eglTerminate(ctx->display); 110 | free(ctx); 111 | return NULL; 112 | } 113 | 114 | static const EGLint context_attributes[] = { 115 | EGL_CONTEXT_CLIENT_VERSION, 2, 116 | EGL_NONE 117 | }; 118 | ctx->context = 119 | eglCreateContext(ctx->display, 120 | config, 121 | EGL_NO_CONTEXT, 122 | context_attributes); 123 | if (ctx->context == EGL_NO_CONTEXT) { 124 | last_error = "could not create EGL context"; 125 | eglTerminate(ctx->display); 126 | free(ctx); 127 | return NULL; 128 | } 129 | 130 | ctx->native_window[0] = native_surface; 131 | ctx->native_window[1] = surface_width; 132 | ctx->native_window[2] = surface_height; 133 | ctx->surface = 134 | eglCreateWindowSurface(ctx->display, config, ctx->native_window, NULL); 135 | if (ctx->surface == EGL_NO_SURFACE) { 136 | last_error = egl_error_string("eglCreateWindowSurface", eglGetError()); 137 | eglDestroyContext(ctx->display, ctx->context); 138 | eglTerminate(ctx->display); 139 | free(ctx); 140 | return NULL; 141 | } 142 | 143 | if (eglMakeCurrent(ctx->display, 144 | ctx->surface, 145 | ctx->surface, 146 | ctx->context) == EGL_FALSE) { 147 | last_error = egl_error_string("eglMakeCurrent", eglGetError()); 148 | eglDestroyContext(ctx->display, ctx->context); 149 | eglTerminate(ctx->display); 150 | free(ctx); 151 | return NULL; 152 | } 153 | 154 | // Don't wait for vsync. 155 | eglSwapInterval(ctx->display, 0); 156 | 157 | return ctx; 158 | } 159 | 160 | void deinit_EGL(EGL_context *ctx) 161 | { 162 | if (eglMakeCurrent(ctx->display, 163 | EGL_NO_SURFACE, 164 | EGL_NO_SURFACE, 165 | EGL_NO_CONTEXT) != EGL_TRUE) { 166 | // fprintf(stderr, "MakeCUrrent failed\n"); 167 | } 168 | if (eglDestroySurface(ctx->display, ctx->surface) != EGL_TRUE) { 169 | // fprintf(stderr, "eglDestroySurface failed\n"); 170 | } 171 | if (eglDestroyContext(ctx->display, ctx->context) != EGL_TRUE) { 172 | // fprintf(stderr, "eglDestroyContext failed\n"); 173 | } 174 | if (eglTerminate(ctx->display) != EGL_TRUE) { 175 | // fprintf(stderr, "eglTerminate failed\n"); 176 | } 177 | free(ctx); 178 | } 179 | 180 | void EGL_swap_buffers(EGL_context *ctx) 181 | { 182 | eglSwapBuffers(ctx->display, ctx->surface); 183 | } 184 | -------------------------------------------------------------------------------- /c/libshade/egl.h: -------------------------------------------------------------------------------- 1 | #ifndef EGL_included 2 | #define EGL_included 3 | 4 | #include 5 | #include 6 | 7 | typedef struct EGL_context EGL_context; 8 | 9 | extern EGL_context *init_EGL(uint32_t native_surface, 10 | uint32_t surface_width, 11 | uint32_t surface_height); 12 | extern void deinit_EGL(EGL_context *); 13 | 14 | extern const char *EGL_last_error(void); 15 | 16 | extern void EGL_swap_buffers(EGL_context *); 17 | 18 | #endif /* !EGL_included */ 19 | -------------------------------------------------------------------------------- /c/libshade/exec.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "exec.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "bcm.h" 9 | #include "queue.h" 10 | #include "render.h" 11 | 12 | #define FBQ_SIZE 200 13 | #define CBQ_SIZE 200 14 | 15 | struct exec { 16 | // borrowed objects 17 | bcm_context *bcm; 18 | LEDs_context *leds; 19 | 20 | // start/stop control 21 | bool running; 22 | bool shutdown; 23 | unsigned running_count; 24 | pthread_cond_t running_cond; 25 | pthread_mutex_t running_lock; 26 | 27 | // FPS calculation 28 | unsigned frame_count; 29 | struct timespec time_zero; 30 | pthread_mutex_t fps_lock; 31 | 32 | // current program 33 | const prog *prog; 34 | pthread_mutex_t prog_lock; 35 | 36 | // worker threads 37 | pthread_t render_thread; 38 | pthread_t cmd_thread; 39 | pthread_t output_thread; 40 | 41 | // inter-worker queues 42 | queue *framebuffer_queue; 43 | queue *cmdbuffer_queue; 44 | 45 | // inter-worker data buffers 46 | LED_pixel *framebuffers[FBQ_SIZE]; 47 | LED_cmd *cmdbuffers[CBQ_SIZE]; 48 | }; 49 | 50 | static void thread_started(exec *ex, const char *name) 51 | { 52 | pthread_setname_np(pthread_self(), name); 53 | pthread_mutex_lock(&ex->running_lock); 54 | ex->running_count++; 55 | pthread_mutex_unlock(&ex->running_lock); 56 | } 57 | 58 | static void thread_finished(exec *ex) 59 | { 60 | pthread_mutex_lock(&ex->running_lock); 61 | ex->running_count--; 62 | pthread_mutex_unlock(&ex->running_lock); 63 | } 64 | 65 | static bool check_running(exec *ex) 66 | { 67 | bool go = true; 68 | pthread_mutex_lock(&ex->running_lock); 69 | while (!ex->running && !ex->shutdown) { 70 | ex->running_count--; 71 | pthread_cond_broadcast(&ex->running_cond); 72 | pthread_cond_wait(&ex->running_cond, &ex->running_lock); 73 | ex->running_count++; 74 | } 75 | go = !ex->shutdown; 76 | pthread_mutex_unlock(&ex->running_lock); 77 | return go; 78 | } 79 | 80 | static void shutdown(exec *ex) 81 | { 82 | pthread_mutex_lock(&ex->running_lock); 83 | ex->shutdown = true; 84 | pthread_cond_broadcast(&ex->running_cond); 85 | pthread_mutex_unlock(&ex->running_lock); 86 | } 87 | 88 | static const prog *get_prog(exec *ex) 89 | { 90 | pthread_mutex_lock(&ex->prog_lock); 91 | const prog *pp = ex->prog; 92 | pthread_mutex_unlock(&ex->prog_lock); 93 | return pp; 94 | } 95 | 96 | static void set_prog(exec *ex, const prog *pp) 97 | { 98 | pthread_mutex_lock(&ex->prog_lock); 99 | ex->prog = pp; 100 | pthread_mutex_unlock(&ex->prog_lock); 101 | } 102 | 103 | static void reset_fps(exec *ex) 104 | { 105 | pthread_mutex_lock(&ex->fps_lock); 106 | clock_gettime(CLOCK_MONOTONIC, &ex->time_zero); 107 | ex->frame_count = 0; 108 | pthread_mutex_unlock(&ex->fps_lock); 109 | } 110 | 111 | static void count_frame(exec *ex) 112 | { 113 | pthread_mutex_lock(&ex->fps_lock); 114 | ex->frame_count++; 115 | pthread_mutex_unlock(&ex->fps_lock); 116 | } 117 | 118 | static void *render_thread_main(void *user_data) 119 | { 120 | exec *ex = user_data; 121 | thread_started(ex, "SHD Render"); 122 | 123 | render_state *rs = render_init(ex->bcm); 124 | while (check_running(ex)) { 125 | const prog *pp = get_prog(ex); 126 | render_frame(rs, pp); 127 | size_t index = queue_acquire_empty(ex->framebuffer_queue); 128 | LED_pixel *pixels = ex->framebuffers[index]; 129 | bcm_read_pixels(ex->bcm, pixels, LEDs_framebuffer_pitch(ex->leds)); 130 | queue_release_full(ex->framebuffer_queue); 131 | } 132 | render_deinit(rs); 133 | thread_finished(ex); 134 | return NULL; 135 | } 136 | 137 | static void *cmd_thread_main(void *user_data) 138 | { 139 | exec *ex = user_data; 140 | thread_started(ex, "SHD cmd"); 141 | while (check_running(ex)) { 142 | size_t cb_idx = queue_acquire_empty(ex->cmdbuffer_queue); 143 | size_t fb_idx = queue_acquire_full(ex->framebuffer_queue); 144 | 145 | LED_pixel *pixels = ex->framebuffers[fb_idx]; 146 | LED_cmd *cmds = ex->cmdbuffers[cb_idx]; 147 | LEDs_create_cmds(ex->leds, pixels, cmds); 148 | 149 | queue_release_full(ex->cmdbuffer_queue); 150 | queue_release_empty(ex->framebuffer_queue); 151 | } 152 | thread_finished(ex); 153 | return NULL; 154 | } 155 | 156 | static void *output_thread_main(void *user_data) 157 | { 158 | exec *ex = user_data; 159 | thread_started(ex, "SHD Output"); 160 | while (check_running(ex)) { 161 | size_t index = queue_acquire_full(ex->cmdbuffer_queue); 162 | LED_cmd *cmds = ex->cmdbuffers[index]; 163 | 164 | LEDs_write_cmds(ex->leds, cmds); 165 | 166 | queue_release_empty(ex->cmdbuffer_queue); 167 | 168 | count_frame(ex); 169 | } 170 | thread_finished(ex); 171 | return NULL; 172 | } 173 | 174 | exec *create_exec(bcm_context *bcm, LEDs_context *leds) 175 | { 176 | exec *ex = calloc(1, sizeof *ex); 177 | if (!ex) 178 | goto FAIL; 179 | 180 | ex->bcm = bcm; 181 | ex->leds = leds; 182 | 183 | ex->framebuffer_queue = create_queue(FBQ_SIZE); 184 | ex->cmdbuffer_queue = create_queue(CBQ_SIZE); 185 | 186 | if (pthread_cond_init(&ex->running_cond, NULL)) 187 | goto FAIL; 188 | 189 | if (pthread_mutex_init(&ex->running_lock, NULL)) 190 | goto FAIL; 191 | 192 | if (pthread_mutex_init(&ex->prog_lock, NULL)) 193 | goto FAIL; 194 | 195 | for (size_t i = 0; i < FBQ_SIZE; i++) 196 | ex->framebuffers[i] = LEDs_alloc_framebuffer(leds); 197 | 198 | for (size_t i = 0; i < CBQ_SIZE; i++) 199 | ex->cmdbuffers[i] = LEDs_alloc_cmdbuffer(leds); 200 | 201 | if (pthread_create(&ex->render_thread, NULL, render_thread_main, ex)) 202 | goto FAIL; 203 | 204 | if (pthread_create(&ex->cmd_thread, NULL, cmd_thread_main, ex)) 205 | goto FAIL; 206 | 207 | if (pthread_create(&ex->output_thread, NULL, output_thread_main, ex)) 208 | goto FAIL; 209 | 210 | return ex; 211 | 212 | FAIL: 213 | if (ex) 214 | destroy_exec(ex); 215 | return NULL; 216 | } 217 | 218 | void destroy_exec(exec *ex) 219 | { 220 | shutdown(ex); 221 | 222 | if (ex->output_thread) { 223 | // pthread_cancel(ex->output_thread); 224 | pthread_join(ex->output_thread, NULL); 225 | } 226 | if (ex->cmd_thread) { 227 | // pthread_cancel(ex->cmd_thread); 228 | pthread_join(ex->cmd_thread, NULL); 229 | } 230 | if (ex->render_thread) { 231 | // pthread_cancel(ex->render_thread); 232 | pthread_join(ex->render_thread, NULL); 233 | } 234 | 235 | for (size_t i = 0; i < FBQ_SIZE; i++) 236 | if (ex->framebuffers[i]) 237 | LEDs_free_framebuffer(ex->framebuffers[i]); 238 | 239 | for (size_t i = 0; i < CBQ_SIZE; i++) 240 | if (ex->cmdbuffers[i]) 241 | LEDs_free_cmdbuffer(ex->cmdbuffers[i]); 242 | 243 | if (ex->cmdbuffer_queue) 244 | destroy_queue(ex->cmdbuffer_queue); 245 | if (ex->framebuffer_queue) 246 | destroy_queue(ex->framebuffer_queue); 247 | 248 | (void)pthread_mutex_destroy(&ex->prog_lock); 249 | (void)pthread_cond_destroy(&ex->running_cond); 250 | (void)pthread_mutex_destroy(&ex->running_lock); 251 | 252 | free(ex); 253 | } 254 | 255 | void exec_start(exec *ex) 256 | { 257 | pthread_mutex_lock(&ex->running_lock); 258 | ex->running = true; 259 | pthread_cond_broadcast(&ex->running_cond); 260 | pthread_mutex_unlock(&ex->running_lock); 261 | reset_fps(ex); 262 | } 263 | 264 | 265 | void exec_stop(exec *ex) 266 | { 267 | pthread_mutex_lock(&ex->running_lock); 268 | ex->running = false; 269 | while (ex->running_count) { 270 | pthread_cond_wait(&ex->running_cond, &ex->running_lock); 271 | } 272 | pthread_mutex_unlock(&ex->running_lock); 273 | } 274 | 275 | double exec_fps(exec *ex) 276 | { 277 | struct timespec now; 278 | clock_gettime(CLOCK_MONOTONIC, &now); 279 | pthread_mutex_lock(&ex->fps_lock); 280 | double seconds = (now.tv_nsec - ex->time_zero.tv_nsec) / 1.e9; 281 | seconds += now.tv_sec - ex->time_zero.tv_sec; 282 | double fps = ex->frame_count / seconds; 283 | pthread_mutex_unlock(&ex->fps_lock); 284 | return fps; 285 | } 286 | 287 | void exec_use_prog(exec *ex, const prog *pp) 288 | { 289 | set_prog(ex, pp); 290 | reset_fps(ex); 291 | } 292 | -------------------------------------------------------------------------------- /c/libshade/exec.h: -------------------------------------------------------------------------------- 1 | #ifndef EXEC_included 2 | #define EXEC_included 3 | 4 | #include "bcm.h" 5 | #include "egl.h" 6 | #include "leds.h" 7 | #include "prog.h" 8 | 9 | typedef struct exec exec; 10 | 11 | extern exec *create_exec(bcm_context *, LEDs_context *); 12 | extern void destroy_exec(exec *); 13 | 14 | extern void exec_start(exec *); 15 | extern void exec_stop(exec *); 16 | 17 | extern double exec_fps(exec *); 18 | 19 | extern void exec_use_prog(exec *, const prog *); 20 | 21 | #endif /* !EXEC_included */ 22 | -------------------------------------------------------------------------------- /c/libshade/leds.c: -------------------------------------------------------------------------------- 1 | #include "leds.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mpsse.h" 10 | 11 | #define FRONT_PORCH_BYTES 7 /* could be 8 */ 12 | #define BACK_PORCH_BYTES 14 13 | 14 | struct LEDs_context { 15 | size_t framebuffer_width; 16 | size_t framebuffer_height; 17 | size_t framebuffer_offset; 18 | size_t led_width; 19 | size_t led_height; 20 | size_t cmdbuf_size; 21 | size_t best_offset; 22 | size_t best_row_pitch; 23 | size_t best_buffer_size; 24 | }; 25 | 26 | LEDs_context *init_LEDs(size_t led_width, 27 | size_t led_height, 28 | size_t framebuffer_width, 29 | size_t framebuffer_height, 30 | size_t framebuffer_offset) 31 | { 32 | int ifnum = 0; 33 | const char *devstr = NULL; 34 | bool slow_clock = false; 35 | mpsse_init(ifnum, devstr, slow_clock); 36 | 37 | LEDs_context *ctx = calloc(1, sizeof *ctx); 38 | ctx->framebuffer_width = framebuffer_width; 39 | ctx->framebuffer_height = framebuffer_height; 40 | ctx->framebuffer_offset = framebuffer_offset; 41 | ctx->led_width = led_width; 42 | ctx->led_height = led_height; 43 | size_t row_pix_bytes = led_width * sizeof (LED_pixel); 44 | size_t row_bytes = FRONT_PORCH_BYTES + row_pix_bytes + BACK_PORCH_BYTES; 45 | ctx->cmdbuf_size = led_height * row_bytes; 46 | ctx->best_offset = FRONT_PORCH_BYTES; 47 | ctx->best_row_pitch = (FRONT_PORCH_BYTES / sizeof (uint16_t) + 48 | led_width + 49 | BACK_PORCH_BYTES / sizeof (uint16_t));; 50 | ctx->best_buffer_size = led_height * ctx->best_row_pitch; 51 | return ctx; 52 | } 53 | 54 | void deinit_LEDs(LEDs_context *ctx) 55 | { 56 | mpsse_close(); 57 | free(ctx); 58 | } 59 | 60 | LED_pixel *LEDs_alloc_framebuffer(LEDs_context *ctx) 61 | { 62 | size_t count = ctx->framebuffer_width * ctx->framebuffer_height; 63 | return calloc(count, sizeof (LED_pixel)); 64 | } 65 | 66 | LED_cmd *LEDs_alloc_cmdbuffer(LEDs_context *ctx) 67 | { 68 | return calloc(ctx->cmdbuf_size, sizeof (LED_cmd)); 69 | } 70 | 71 | void LEDs_free_framebuffer(LED_pixel *framebuffer) 72 | { 73 | free(framebuffer); 74 | } 75 | 76 | void LEDs_free_cmdbuffer(LED_cmd *cmdbuffer) 77 | { 78 | free(cmdbuffer); 79 | } 80 | 81 | size_t LEDs_framebuffer_pitch(LEDs_context *ctx) 82 | { 83 | return ctx->framebuffer_width; 84 | } 85 | 86 | size_t LEDs_best_buffer_size(const LEDs_context *ctx) 87 | { 88 | return ctx->best_buffer_size; 89 | } 90 | 91 | size_t LEDs_best_offset(const LEDs_context *ctx) 92 | { 93 | return ctx->best_offset; 94 | } 95 | 96 | size_t LEDs_best_row_pitch(const LEDs_context *ctx) 97 | { 98 | return ctx->best_row_pitch; 99 | } 100 | 101 | void LEDs_create_cmds(LEDs_context *ctx, 102 | const LED_pixel *pixels, 103 | LED_cmd *cmds) 104 | { 105 | size_t fb_row_pitch = ctx->framebuffer_width; 106 | size_t fb_offset = ctx->framebuffer_offset; 107 | size_t row_size = ctx->led_width * sizeof (LED_pixel); 108 | size_t row_count = ctx->led_height; 109 | size_t cmd_idx = 0; 110 | for (size_t row = 0; row < row_count; row++) { 111 | // Set CS low 112 | cmds[cmd_idx++] = 0x80; // MC_SETB_LOW 113 | cmds[cmd_idx++] = 0x00; // gpio 114 | cmds[cmd_idx++] = 0x2b; // dir 115 | 116 | // SPI packet header 117 | cmds[cmd_idx++] = 0x11; 118 | cmds[cmd_idx++] = (row_size + 1 - 1) & 0xFF; 119 | cmds[cmd_idx++] = (row_size + 1 - 1) >> 8; 120 | 121 | // SPI payload 122 | cmds[cmd_idx++] = 0x80; 123 | size_t y = row; 124 | memcpy(cmds + cmd_idx, 125 | &pixels[y * fb_row_pitch + fb_offset], 126 | row_size); 127 | cmd_idx += row_size; 128 | 129 | // Set CS high 130 | cmds[cmd_idx++] = 0x80; // MZC_SETB_LOW 131 | cmds[cmd_idx++] = 0x28; // gpio 132 | cmds[cmd_idx++] = 0x2b; // dir 133 | 134 | // Set CS low 135 | cmds[cmd_idx++] = 0x80; // MC_SETB_LOW 136 | cmds[cmd_idx++] = 0x00; // gpio 137 | cmds[cmd_idx++] = 0x2b; // dir 138 | 139 | // SPI header 140 | cmds[cmd_idx++] = 0x11; // MC_DATA_OUT | MC_DATA_OCN 141 | cmds[cmd_idx++] = 2-1; 142 | cmds[cmd_idx++] = 0; 143 | 144 | // SPI payload 145 | cmds[cmd_idx++] = 0x03; 146 | cmds[cmd_idx++] = row; 147 | 148 | // Set CS high 149 | cmds[cmd_idx++] = 0x80; // MC_SETB_LOW 150 | cmds[cmd_idx++] = 0x28; // gpio 151 | cmds[cmd_idx++] = 0x2b; // dir 152 | } 153 | assert(cmd_idx == ctx->cmdbuf_size); 154 | } 155 | 156 | static void set_cs(int cs_b) 157 | { 158 | uint8_t gpio = cs_b ? 0x28 : 0; 159 | uint8_t direction = 0x2b; 160 | mpsse_set_gpio(gpio, direction); 161 | } 162 | 163 | void LEDs_write_cmds(LEDs_context *ctx, const LED_cmd *cmds) 164 | { 165 | mpsse_send_raw((uint8_t *)cmds, ctx->cmdbuf_size); 166 | 167 | // Swap 168 | unsigned char cmd_buf[2]; 169 | set_cs(0); 170 | cmd_buf[0] = 0x04; 171 | cmd_buf[1] = 0x00; 172 | mpsse_send_spi(cmd_buf, 2); 173 | set_cs(1); 174 | } 175 | 176 | void LEDs_await_vsync(LEDs_context *ctx) 177 | { 178 | uint8_t spi_buf[2]; 179 | do { 180 | spi_buf[0] = 0x00; 181 | spi_buf[1] = 0x00; 182 | set_cs(0); 183 | mpsse_xfer_spi(spi_buf, 2); 184 | set_cs(1); 185 | } while (((spi_buf[0] | spi_buf[1]) & 0x02) != 0x02); 186 | } 187 | -------------------------------------------------------------------------------- /c/libshade/leds.h: -------------------------------------------------------------------------------- 1 | #ifndef LEDS_included 2 | #define LEDS_included 3 | 4 | #include 5 | #include 6 | 7 | typedef struct LEDs_context LEDs_context; 8 | typedef uint16_t LED_pixel; 9 | typedef uint8_t LED_cmd; 10 | 11 | extern LEDs_context *init_LEDs(size_t LEDs_width, 12 | size_t LEDs_height, 13 | size_t framebuffer_width, 14 | size_t framebuffer_height, 15 | size_t framebuffer_offset); 16 | extern void deinit_LEDs(LEDs_context *); 17 | 18 | extern LED_pixel *LEDs_alloc_framebuffer(LEDs_context *); 19 | extern LED_cmd *LEDs_alloc_cmdbuffer(LEDs_context *); 20 | extern void LEDs_free_framebuffer(LED_pixel *); 21 | extern void LEDs_free_cmdbuffer(LED_cmd *); 22 | extern size_t LEDs_framebuffer_pitch(LEDs_context *); 23 | 24 | extern size_t LEDs_best_buffer_size(const LEDs_context *); 25 | extern size_t LEDs_best_offset(const LEDs_context *); 26 | extern size_t LEDs_best_row_pitch(const LEDs_context *); 27 | 28 | extern void LEDs_create_cmds(LEDs_context *, 29 | const LED_pixel *, 30 | LED_cmd *); 31 | extern void LEDs_write_cmds(LEDs_context *, const LED_cmd *); 32 | 33 | // extern void LEDs_write_pixels(LEDs_context *, 34 | // LED_pixel *pixel_buf, 35 | // size_t row_offset, 36 | // size_t row_pitch); 37 | 38 | extern void LEDs_await_vsync(LEDs_context *); 39 | 40 | #endif /* !LEDS_included */ 41 | -------------------------------------------------------------------------------- /c/libshade/mpsse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * iceprog -- simple programming tool for FTDI-based Lattice iCE programmers 3 | * 4 | * Copyright (C) 2015 Clifford Wolf 5 | * Copyright (C) 2018 Piotr Esden-Tempski 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | * Relevant Documents: 20 | * ------------------- 21 | * http://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf 22 | */ 23 | 24 | #define _GNU_SOURCE 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "mpsse.h" 34 | 35 | // --------------------------------------------------------- 36 | // MPSSE / FTDI definitions 37 | // --------------------------------------------------------- 38 | 39 | /* FTDI bank pinout typically used for iCE dev boards 40 | * BUS IO | Signal | Control 41 | * -------+--------+-------------- 42 | * xDBUS0 | SCK | MPSSE 43 | * xDBUS1 | MOSI | MPSSE 44 | * xDBUS2 | MISO | MPSSE 45 | * xDBUS3 | nc | 46 | * xDBUS4 | CS | GPIO 47 | * xDBUS5 | nc | 48 | * xDBUS6 | CDONE | GPIO 49 | * xDBUS7 | CRESET | GPIO 50 | */ 51 | 52 | struct ftdi_context mpsse_ftdic; 53 | bool mpsse_ftdic_open = false; 54 | bool mpsse_ftdic_latency_set = false; 55 | unsigned char mpsse_ftdi_latency; 56 | 57 | /* MPSSE engine command definitions */ 58 | enum mpsse_cmd 59 | { 60 | /* Mode commands */ 61 | MC_SETB_LOW = 0x80, /* Set Data bits LowByte */ 62 | MC_READB_LOW = 0x81, /* Read Data bits LowByte */ 63 | MC_SETB_HIGH = 0x82, /* Set Data bits HighByte */ 64 | MC_READB_HIGH = 0x83, /* Read data bits HighByte */ 65 | MC_LOOPBACK_EN = 0x84, /* Enable loopback */ 66 | MC_LOOPBACK_DIS = 0x85, /* Disable loopback */ 67 | MC_SET_CLK_DIV = 0x86, /* Set clock divisor */ 68 | MC_FLUSH = 0x87, /* Flush buffer fifos to the PC. */ 69 | MC_WAIT_H = 0x88, /* Wait on GPIOL1 to go high. */ 70 | MC_WAIT_L = 0x89, /* Wait on GPIOL1 to go low. */ 71 | MC_TCK_X5 = 0x8A, /* Disable /5 div, enables 60MHz master clock */ 72 | MC_TCK_D5 = 0x8B, /* Enable /5 div, backward compat to FT2232D */ 73 | MC_EN_3PH_CLK = 0x8C, /* Enable 3 phase clk, DDR I2C */ 74 | MC_DIS_3PH_CLK = 0x8D, /* Disable 3 phase clk */ 75 | MC_CLK_N = 0x8E, /* Clock every bit, used for JTAG */ 76 | MC_CLK_N8 = 0x8F, /* Clock every byte, used for JTAG */ 77 | MC_CLK_TO_H = 0x94, /* Clock until GPIOL1 goes high */ 78 | MC_CLK_TO_L = 0x95, /* Clock until GPIOL1 goes low */ 79 | MC_EN_ADPT_CLK = 0x96, /* Enable adaptive clocking */ 80 | MC_DIS_ADPT_CLK = 0x97, /* Disable adaptive clocking */ 81 | MC_CLK8_TO_H = 0x9C, /* Clock until GPIOL1 goes high, count bytes */ 82 | MC_CLK8_TO_L = 0x9D, /* Clock until GPIOL1 goes low, count bytes */ 83 | MC_TRI = 0x9E, /* Set IO to only drive on 0 and tristate on 1 */ 84 | /* CPU mode commands */ 85 | MC_CPU_RS = 0x90, /* CPUMode read short address */ 86 | MC_CPU_RE = 0x91, /* CPUMode read extended address */ 87 | MC_CPU_WS = 0x92, /* CPUMode write short address */ 88 | MC_CPU_WE = 0x93, /* CPUMode write extended address */ 89 | }; 90 | 91 | /* Transfer Command bits */ 92 | 93 | /* All byte based commands consist of: 94 | * - Command byte 95 | * - Length lsb 96 | * - Length msb 97 | * 98 | * If data out is enabled the data follows after the above command bytes, 99 | * otherwise no additional data is needed. 100 | * - Data * n 101 | * 102 | * All bit based commands consist of: 103 | * - Command byte 104 | * - Length 105 | * 106 | * If data out is enabled a byte containing bitst to transfer follows. 107 | * Otherwise no additional data is needed. Only up to 8 bits can be transferred 108 | * per transaction when in bit mode. 109 | */ 110 | 111 | /* b 0000 0000 112 | * |||| |||`- Data out negative enable. Update DO on negative clock edge. 113 | * |||| ||`-- Bit count enable. When reset count represents bytes. 114 | * |||| |`--- Data in negative enable. Latch DI on negative clock edge. 115 | * |||| `---- LSB enable. When set clock data out LSB first. 116 | * |||| 117 | * |||`------ Data out enable 118 | * ||`------- Data in enable 119 | * |`-------- TMS mode enable 120 | * `--------- Special command mode enable. See mpsse_cmd enum. 121 | */ 122 | 123 | #define MC_DATA_TMS (0x40) /* When set use TMS mode */ 124 | #define MC_DATA_IN (0x20) /* When set read data (Data IN) */ 125 | #define MC_DATA_OUT (0x10) /* When set write data (Data OUT) */ 126 | #define MC_DATA_LSB (0x08) /* When set input/output data LSB first. */ 127 | #define MC_DATA_ICN (0x04) /* When set receive data on negative clock edge */ 128 | #define MC_DATA_BITS (0x02) /* When set count bits not bytes */ 129 | #define MC_DATA_OCN (0x01) /* When set update data on negative clock edge */ 130 | 131 | // --------------------------------------------------------- 132 | // MPSSE / FTDI function implementations 133 | // --------------------------------------------------------- 134 | 135 | void mpsse_check_rx() 136 | { 137 | while (1) { 138 | uint8_t data; 139 | int rc = ftdi_read_data(&mpsse_ftdic, &data, 1); 140 | if (rc <= 0) 141 | break; 142 | fprintf(stderr, "unexpected rx byte: %02X\n", data); 143 | } 144 | } 145 | 146 | void mpsse_error(int status) 147 | { 148 | mpsse_check_rx(); 149 | fprintf(stderr, "ABORT.\n"); 150 | if (mpsse_ftdic_open) { 151 | if (mpsse_ftdic_latency_set) 152 | ftdi_set_latency_timer(&mpsse_ftdic, mpsse_ftdi_latency); 153 | ftdi_usb_close(&mpsse_ftdic); 154 | } 155 | ftdi_deinit(&mpsse_ftdic); 156 | exit(status); 157 | } 158 | 159 | uint8_t mpsse_recv_byte() 160 | { 161 | uint8_t data; 162 | while (1) { 163 | int rc = ftdi_read_data(&mpsse_ftdic, &data, 1); 164 | if (rc < 0) { 165 | fprintf(stderr, "Read error.\n"); 166 | mpsse_error(2); 167 | } 168 | if (rc == 1) 169 | break; 170 | usleep(100); 171 | } 172 | return data; 173 | } 174 | 175 | void mpsse_send_byte(uint8_t data) 176 | { 177 | int rc = ftdi_write_data(&mpsse_ftdic, &data, 1); 178 | if (rc != 1) { 179 | fprintf(stderr, "Write error (single byte, rc=%d, expected %d).\n", rc, 1); 180 | mpsse_error(2); 181 | } 182 | } 183 | 184 | void mpsse_send_spi(uint8_t *data, int n) 185 | { 186 | if (n < 1) 187 | return; 188 | 189 | /* Output only, update data on negative clock edge. */ 190 | mpsse_send_byte(MC_DATA_OUT | MC_DATA_OCN); 191 | mpsse_send_byte(n - 1); 192 | mpsse_send_byte((n - 1) >> 8); 193 | 194 | int rc = ftdi_write_data(&mpsse_ftdic, data, n); 195 | if (rc != n) { 196 | fprintf(stderr, "Write error (chunk, rc=%d, expected %d).\n", rc, n); 197 | mpsse_error(2); 198 | } 199 | } 200 | 201 | void mpsse_send_raw(uint8_t *data, int n) 202 | { 203 | int rc = ftdi_write_data(&mpsse_ftdic, data, n); 204 | if (rc != n) { 205 | fprintf(stderr, "Write error (chunk, rc=%d, expected %d).\n", rc, n); 206 | mpsse_error(2); 207 | } 208 | } 209 | 210 | void mpsse_xfer_spi(uint8_t *data, int n) 211 | { 212 | if (n < 1) 213 | return; 214 | 215 | /* Input and output, update data on negative edge read on positive. */ 216 | mpsse_send_byte(MC_DATA_IN | MC_DATA_OUT | MC_DATA_OCN); 217 | mpsse_send_byte(n - 1); 218 | mpsse_send_byte((n - 1) >> 8); 219 | 220 | int rc = ftdi_write_data(&mpsse_ftdic, data, n); 221 | if (rc != n) { 222 | fprintf(stderr, "Write error (chunk, rc=%d, expected %d).\n", rc, n); 223 | mpsse_error(2); 224 | } 225 | 226 | for (int i = 0; i < n; i++) 227 | data[i] = mpsse_recv_byte(); 228 | } 229 | 230 | uint8_t mpsse_xfer_spi_bits(uint8_t data, int n) 231 | { 232 | if (n < 1) 233 | return 0; 234 | 235 | /* Input and output, update data on negative edge read on positive, bits. */ 236 | mpsse_send_byte(MC_DATA_IN | MC_DATA_OUT | MC_DATA_OCN | MC_DATA_BITS); 237 | mpsse_send_byte(n - 1); 238 | mpsse_send_byte(data); 239 | 240 | return mpsse_recv_byte(); 241 | } 242 | 243 | void mpsse_set_gpio(uint8_t gpio, uint8_t direction) 244 | { 245 | mpsse_send_byte(MC_SETB_LOW); 246 | mpsse_send_byte(gpio); /* Value */ 247 | mpsse_send_byte(direction); /* Direction */ 248 | } 249 | 250 | int mpsse_readb_low(void) 251 | { 252 | uint8_t data; 253 | mpsse_send_byte(MC_READB_LOW); 254 | data = mpsse_recv_byte(); 255 | return data; 256 | } 257 | 258 | int mpsse_readb_high(void) 259 | { 260 | uint8_t data; 261 | mpsse_send_byte(MC_READB_HIGH); 262 | data = mpsse_recv_byte(); 263 | return data; 264 | } 265 | 266 | void mpsse_send_dummy_bytes(uint8_t n) 267 | { 268 | // add 8 x count dummy bits (aka n bytes) 269 | mpsse_send_byte(MC_CLK_N8); 270 | mpsse_send_byte(n - 1); 271 | mpsse_send_byte(0x00); 272 | 273 | } 274 | 275 | void mpsse_send_dummy_bit(void) 276 | { 277 | // add 1 dummy bit 278 | mpsse_send_byte(MC_CLK_N); 279 | mpsse_send_byte(0x00); 280 | } 281 | 282 | void mpsse_init(int ifnum, const char *devstr, bool slow_clock) 283 | { 284 | enum ftdi_interface ftdi_ifnum = INTERFACE_A; 285 | 286 | switch (ifnum) { 287 | case 0: 288 | ftdi_ifnum = INTERFACE_A; 289 | break; 290 | case 1: 291 | ftdi_ifnum = INTERFACE_B; 292 | break; 293 | case 2: 294 | ftdi_ifnum = INTERFACE_C; 295 | break; 296 | case 3: 297 | ftdi_ifnum = INTERFACE_D; 298 | break; 299 | default: 300 | ftdi_ifnum = INTERFACE_A; 301 | break; 302 | } 303 | 304 | ftdi_init(&mpsse_ftdic); 305 | ftdi_set_interface(&mpsse_ftdic, ftdi_ifnum); 306 | 307 | if (devstr != NULL) { 308 | if (ftdi_usb_open_string(&mpsse_ftdic, devstr)) { 309 | fprintf(stderr, "Can't find iCE FTDI USB device (device string %s).\n", devstr); 310 | mpsse_error(2); 311 | } 312 | } else { 313 | if (ftdi_usb_open(&mpsse_ftdic, 0x0403, 0x6010) && ftdi_usb_open(&mpsse_ftdic, 0x0403, 0x6014)) { 314 | fprintf(stderr, "Can't find iCE FTDI USB device (vendor_id 0x0403, device_id 0x6010 or 0x6014).\n"); 315 | mpsse_error(2); 316 | } 317 | } 318 | 319 | mpsse_ftdic_open = true; 320 | 321 | if (ftdi_usb_reset(&mpsse_ftdic)) { 322 | fprintf(stderr, "Failed to reset iCE FTDI USB device.\n"); 323 | mpsse_error(2); 324 | } 325 | 326 | if (ftdi_usb_purge_buffers(&mpsse_ftdic)) { 327 | fprintf(stderr, "Failed to purge buffers on iCE FTDI USB device.\n"); 328 | mpsse_error(2); 329 | } 330 | 331 | if (ftdi_get_latency_timer(&mpsse_ftdic, &mpsse_ftdi_latency) < 0) { 332 | fprintf(stderr, "Failed to get latency timer (%s).\n", ftdi_get_error_string(&mpsse_ftdic)); 333 | mpsse_error(2); 334 | } 335 | 336 | /* 1 is the fastest polling, it means 1 kHz polling */ 337 | if (ftdi_set_latency_timer(&mpsse_ftdic, 1) < 0) { 338 | fprintf(stderr, "Failed to set latency timer (%s).\n", ftdi_get_error_string(&mpsse_ftdic)); 339 | mpsse_error(2); 340 | } 341 | 342 | mpsse_ftdic_latency_set = true; 343 | 344 | /* Enter MPSSE (Multi-Protocol Synchronous Serial Engine) mode. Set all pins to output. */ 345 | if (ftdi_set_bitmode(&mpsse_ftdic, 0xff, BITMODE_MPSSE) < 0) { 346 | fprintf(stderr, "Failed to set BITMODE_MPSSE on iCE FTDI USB device.\n"); 347 | mpsse_error(2); 348 | } 349 | 350 | if (ftdi_write_data_set_chunksize(&mpsse_ftdic, 65536)) { 351 | fprintf(stderr, "Failed to set write chunk size.\n"); 352 | mpsse_error(2); 353 | } 354 | 355 | // disable clock divide by 5 356 | mpsse_send_byte(MC_TCK_X5); 357 | 358 | if (slow_clock) { 359 | // set 50 kHz clock 360 | mpsse_send_byte(MC_SET_CLK_DIV); 361 | mpsse_send_byte(119); 362 | mpsse_send_byte(0x00); 363 | } else { 364 | // set 6 MHz clock 365 | mpsse_send_byte(MC_SET_CLK_DIV); 366 | mpsse_send_byte(0x00); 367 | mpsse_send_byte(0x00); 368 | } 369 | // 370 | // mpsse_send_byte(MC_SET_CLK_DIV); 371 | // mpsse_send_byte(0x00); 372 | // mpsse_send_byte(0x00); 373 | // // disable clock divide by 5 374 | // mpsse_send_byte(MC_TCK_X5); 375 | // mpsse_send_byte(MC_SET_CLK_DIV); 376 | // mpsse_send_byte(0x00); 377 | // mpsse_send_byte(0x00); 378 | // // disable clock divide by 5 379 | // mpsse_send_byte(MC_TCK_X5); 380 | // mpsse_send_byte(MC_SET_CLK_DIV); 381 | // mpsse_send_byte(0x00); 382 | // mpsse_send_byte(0x00); 383 | // // disable clock divide by 5 384 | // mpsse_send_byte(MC_TCK_X5); 385 | } 386 | 387 | void mpsse_close(void) 388 | { 389 | ftdi_set_latency_timer(&mpsse_ftdic, mpsse_ftdi_latency); 390 | ftdi_disable_bitbang(&mpsse_ftdic); 391 | ftdi_usb_close(&mpsse_ftdic); 392 | ftdi_deinit(&mpsse_ftdic); 393 | } 394 | -------------------------------------------------------------------------------- /c/libshade/mpsse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * iceprog -- simple programming tool for FTDI-based Lattice iCE programmers 3 | * 4 | * Copyright (C) 2015 Clifford Wolf 5 | * Copyright (C) 2018 Piotr Esden-Tempski 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #ifndef MPSSE_H 21 | #define MPSSE_H 22 | 23 | #include 24 | 25 | void mpsse_check_rx(void); 26 | void mpsse_error(int status); 27 | uint8_t mpsse_recv_byte(void); 28 | void mpsse_send_byte(uint8_t data); 29 | void mpsse_send_spi(uint8_t *data, int n); 30 | void mpsse_xfer_spi(uint8_t *data, int n); 31 | uint8_t mpsse_xfer_spi_bits(uint8_t data, int n); 32 | void mpsse_set_gpio(uint8_t gpio, uint8_t direction); 33 | int mpsse_readb_low(void); 34 | int mpsse_readb_high(void); 35 | void mpsse_send_dummy_bytes(uint8_t n); 36 | void mpsse_send_dummy_bit(void); 37 | void mpsse_init(int ifnum, const char *devstr, bool slow_clock); 38 | void mpsse_close(void); 39 | void mpsse_send_raw(uint8_t *data, int n); 40 | 41 | #endif /* MPSSE_H */ 42 | -------------------------------------------------------------------------------- /c/libshade/prog.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "prog.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | typedef struct image_info { 14 | char *name; 15 | size_t width; 16 | size_t height; 17 | uint8_t *data; 18 | } image_info; 19 | 20 | typedef struct predefined_info { 21 | char *name; 22 | predefined value; 23 | } predefined_info; 24 | 25 | struct shd_prog { 26 | int id; 27 | char *vert_shader_source; 28 | char *frag_shader_source; 29 | size_t image_count; 30 | size_t image_alloc; 31 | image_info *images; 32 | size_t predef_count; 33 | size_t predef_alloc; 34 | predefined_info *predefs; 35 | }; 36 | 37 | static const char *GL_error_str(GLenum err) 38 | { 39 | switch (err) { 40 | 41 | case GL_NO_ERROR: 42 | return "GL_NO_ERROR"; 43 | 44 | case GL_INVALID_ENUM: 45 | return "GL_INVALID_ENUM"; 46 | 47 | case GL_INVALID_VALUE: 48 | return "GL_INVALID_VALUE"; 49 | 50 | case GL_INVALID_OPERATION: 51 | return "GL_INVALID_OPERATION"; 52 | 53 | case GL_OUT_OF_MEMORY: 54 | return "GL_OUT_OF_MEMORY"; 55 | 56 | case GL_INVALID_FRAMEBUFFER_OPERATION: 57 | return "GL_INVALID_FRAMEBUFFER_OPERATION"; 58 | 59 | default: 60 | return "unknown GL error"; 61 | } 62 | } 63 | 64 | static void log_info(char **info_log, char *fmt, ...) 65 | { 66 | if (!info_log) 67 | return; 68 | va_list ap; 69 | va_start(ap, fmt); 70 | vasprintf(info_log, fmt, ap); 71 | va_end(ap); 72 | } 73 | 74 | static bool shader_is_ok(GLuint shader) 75 | { 76 | GLint status = ~GL_TRUE; 77 | glGetShaderiv(shader, GL_COMPILE_STATUS, &status); 78 | return status == GL_TRUE; 79 | } 80 | 81 | prog *create_prog(void) 82 | { 83 | static int next_id; 84 | prog *pp = calloc(1, sizeof *pp); 85 | pp->id = ++next_id; 86 | return pp; 87 | } 88 | 89 | void destroy_prog(prog *pp) 90 | { 91 | free(pp->vert_shader_source); 92 | free(pp->frag_shader_source); 93 | for (size_t i = 0; i < pp->image_count; i++) { 94 | free(pp->images[i].name); 95 | free(pp->images[i].data); 96 | } 97 | free(pp->images); 98 | for (size_t i = 0; i < pp->predef_count; i++) 99 | free(pp->predefs[i].name); 100 | free(pp->predefs); 101 | free(pp); 102 | } 103 | 104 | int prog_id(const prog *pp) 105 | { 106 | return pp->id; 107 | } 108 | 109 | size_t prog_image_count(const prog *pp) 110 | { 111 | return pp->image_count; 112 | } 113 | 114 | const char *prog_image_name(const prog *pp, size_t index) 115 | { 116 | return pp->images[index].name; 117 | } 118 | 119 | size_t prog_image_width(const prog *pp, size_t index) 120 | { 121 | return pp->images[index].width; 122 | } 123 | 124 | size_t prog_image_height(const prog *pp, size_t index) 125 | { 126 | return pp->images[index].height; 127 | } 128 | 129 | const uint8_t *prog_image_data(const prog *pp, size_t index) 130 | { 131 | return pp->images[index].data; 132 | } 133 | 134 | size_t prog_predefined_count(const prog *pp) 135 | { 136 | return pp->predef_count; 137 | } 138 | 139 | const char *prog_predefined_name(const prog *pp, size_t index) 140 | { 141 | if (index < pp->predef_count) 142 | return pp->predefs[index].name; 143 | return NULL; 144 | } 145 | 146 | predefined prog_predefined_value(const prog *pp, size_t index) 147 | { 148 | if (index < pp->predef_count) 149 | return pp->predefs[index].value; 150 | return PD_UNKNOWN; 151 | } 152 | 153 | static bool check_uniform(GLuint prog, 154 | const char *name, 155 | GLint index, 156 | GLenum expected_type, 157 | GLint expected_size, 158 | GLint name_max_length, 159 | char **info_log) 160 | { 161 | GLint u_size; 162 | GLenum u_type; 163 | char u_name[name_max_length]; 164 | glGetActiveUniform(prog, 165 | index, 166 | sizeof u_name, 167 | NULL, 168 | &u_size, 169 | &u_type, 170 | u_name); 171 | if (u_type != expected_type || u_size != expected_size) { 172 | log_info(info_log, 173 | "uniform %s: unexpected type/size %#0x/%d\n", 174 | name, u_type, u_size); 175 | return false; 176 | } 177 | return true; 178 | } 179 | 180 | bool prog_is_okay(const prog *pp, char **info_log) 181 | { 182 | GLuint prog = prog_instantiate(pp, info_log); 183 | if (prog == 0) 184 | return false; 185 | 186 | // Enumerate attributes. Verify that "vert" is the only one. 187 | 188 | GLint attrib_count; 189 | glGetProgramiv(prog, GL_ACTIVE_ATTRIBUTES, &attrib_count); 190 | if (attrib_count != 1) { 191 | return false; 192 | } 193 | GLchar attrib_name[16]; 194 | GLint attrib_size; 195 | GLenum attrib_type; 196 | glGetActiveAttrib(prog, 197 | 0, 198 | sizeof attrib_name, 199 | NULL, 200 | &attrib_size, 201 | &attrib_type, 202 | attrib_name); 203 | if (strcmp(attrib_name, "vert") != 0) { 204 | log_info(info_log, "unexpected attribute: %s", attrib_name); 205 | return false; 206 | } 207 | if (attrib_type != GL_FLOAT_VEC3 || attrib_size != 1) { 208 | log_info(info_log, "`vert' attribute type/size should be GL_VEC3/1"); 209 | } 210 | 211 | // Enumerate uniforms. Verify each is bound with the correct 212 | // type and size. 213 | 214 | GLint uniform_count; 215 | glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &uniform_count); 216 | GLint uniform_max_length; 217 | glGetProgramiv(prog, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniform_max_length); 218 | bool uniform_bound[uniform_count]; 219 | memset(uniform_bound, 0, sizeof uniform_bound); 220 | 221 | // Verify all images in pp. 222 | for (size_t i = 0; i < pp->image_count; i++) { 223 | const image_info *ip = &pp->images[i]; 224 | GLint index = glGetUniformLocation(prog, ip->name); 225 | if (index == -1) 226 | continue; // It's okay to provide unused uniforms. 227 | if (!check_uniform(prog, 228 | ip->name, 229 | index, 230 | GL_SAMPLER_2D, 231 | 1, 232 | uniform_max_length, 233 | info_log)) 234 | return false; 235 | uniform_bound[index] = true; 236 | } 237 | 238 | // Verify all predefineds in pp. 239 | for (size_t i = 0; i < pp->predef_count; i++) { 240 | const predefined_info *pip = &pp->predefs[i]; 241 | GLint index = glGetUniformLocation(prog, pip->name); 242 | if (index == -1) 243 | continue; // It's okay to provide unused uniforms. 244 | GLenum expected_type; 245 | GLint expected_size; 246 | switch (pip->value) { 247 | 248 | case PD_RESOLUTION: 249 | expected_type = GL_FLOAT_VEC3; 250 | expected_size = 1; 251 | break; 252 | 253 | case PD_PLAY_TIME: 254 | expected_type = GL_FLOAT; 255 | expected_size = 1; 256 | break; 257 | 258 | case PD_FRAME: 259 | expected_type = GL_INT; 260 | expected_size = 1; 261 | break; 262 | 263 | case PD_NOISE_SMALL: 264 | expected_type = GL_SAMPLER_2D; 265 | expected_size = 1; 266 | break; 267 | 268 | case PD_NOISE_MEDIUM: 269 | expected_type = GL_SAMPLER_2D; 270 | expected_size = 1; 271 | break; 272 | 273 | default: 274 | log_info(info_log, "unknown predefined %d", pip->value); 275 | return false; 276 | } 277 | if (!check_uniform(prog, 278 | pip->name, 279 | index, 280 | expected_type, 281 | expected_size, 282 | uniform_max_length, 283 | info_log)) { 284 | return false; 285 | } 286 | uniform_bound[index] = true; 287 | } 288 | 289 | for (size_t i = 0; i < uniform_count; i++) { 290 | if (!uniform_bound[i]) { 291 | char u_name[uniform_max_length]; 292 | GLenum u_type; 293 | GLint u_size; 294 | glGetActiveUniform(prog, 295 | i, 296 | sizeof u_name, 297 | NULL, 298 | &u_size, 299 | &u_type, 300 | u_name); 301 | log_info(info_log, "uniform %s is not bound.", u_name); 302 | return false; 303 | 304 | } 305 | } 306 | 307 | // 308 | // get max uniform index 309 | // alloc flag. 310 | 311 | // for all in pp.images: 312 | // if variable in shader: 313 | // verify type 314 | 315 | // for all in pp.predef: 316 | // if variable in shader: 317 | // verify type matches predef type. 318 | 319 | // for all shader uniforms: 320 | // verify either in predef or 321 | 322 | // enumerate uniforms. Verify they are all accounted for. 323 | // for (name, value) in predefined: 324 | // glGetUniformLocation(prog, name) 325 | // glGetActiveUniform(...) 326 | // check type, size against value. 327 | 328 | glDeleteProgram(prog); 329 | return prog != 0; 330 | } 331 | 332 | bool prog_attach_shader(prog *pp, shader_type type, const char *source) 333 | { 334 | switch (type) { 335 | 336 | case PST_VERTEX: 337 | free(pp->vert_shader_source); 338 | pp->vert_shader_source = strdup(source); 339 | return true; 340 | 341 | case PST_FRAGMENT: 342 | free(pp->frag_shader_source); 343 | pp->frag_shader_source = strdup(source); 344 | return true; 345 | 346 | default: 347 | return false; 348 | } 349 | } 350 | 351 | bool prog_attach_image(prog *pp, 352 | const char *name, 353 | size_t width, 354 | size_t height, 355 | uint8_t *data) 356 | { 357 | size_t n = pp->image_count; 358 | if (pp->image_alloc <= n) { 359 | size_t new_alloc = 2 * n + 10; 360 | pp->images = realloc(pp->images, new_alloc * sizeof *pp->images); 361 | pp->image_alloc = new_alloc; 362 | } 363 | // 4: four channels in RGBA 364 | size_t byte_count = width * height * 4 * sizeof *data; 365 | 366 | pp->images[n].name = strdup(name); 367 | pp->images[n].width = width; 368 | pp->images[n].height = height; 369 | pp->images[n].data = malloc(byte_count); 370 | memcpy(pp->images[n].data, data, byte_count); 371 | pp->image_count++; 372 | return true; 373 | } 374 | 375 | bool prog_attach_predefined(prog *pp, const char *name, predefined value) 376 | { 377 | size_t n = pp->predef_count; 378 | if (pp->predef_alloc <= n) { 379 | size_t new_alloc = 2 * n + 10; 380 | pp->predefs = realloc(pp->predefs, new_alloc * sizeof *pp->predefs); 381 | pp->predef_alloc = new_alloc; 382 | } 383 | pp->predefs[n].name = strdup(name); 384 | pp->predefs[n].value = value; 385 | pp->predef_count++; 386 | return true; 387 | } 388 | 389 | static GLuint create_shader(const prog *pp, 390 | GLenum type, 391 | const char *source, 392 | char **info_log) 393 | { 394 | GLuint s = glCreateShader(type); 395 | if (s == 0) { 396 | log_info(info_log, 397 | "glCreateShader failed: %s", GL_error_str(glGetError())); 398 | return 0; 399 | } 400 | GLint length = -1; 401 | glShaderSource(s, 1, &source, &length); 402 | glCompileShader(s); 403 | if (info_log && !shader_is_ok(s)) { 404 | GLint info_len; 405 | glGetShaderiv(s, GL_INFO_LOG_LENGTH, &info_len); 406 | if (info_len > 0) { 407 | *info_log = malloc(info_len); 408 | glGetShaderInfoLog(s, info_len, NULL, *info_log); 409 | } 410 | glDeleteShader(s); 411 | return 0; 412 | } 413 | return s; 414 | } 415 | 416 | GLuint prog_instantiate(const prog *pp, char **info_log) 417 | { 418 | GLuint v = 0, f = 0, p = 0; 419 | 420 | v = create_shader(pp, GL_VERTEX_SHADER, pp->vert_shader_source, info_log); 421 | if (!v) 422 | goto FAIL; 423 | f = create_shader(pp, 424 | GL_FRAGMENT_SHADER, 425 | pp->frag_shader_source, 426 | info_log); 427 | if (!f) 428 | goto FAIL; 429 | p = glCreateProgram(); 430 | if (!p) { 431 | log_info(info_log, 432 | "glCreateProgram failed: %s", GL_error_str(glGetError())); 433 | goto FAIL; 434 | } 435 | 436 | // Link program. 437 | 438 | glAttachShader(p, v); 439 | glAttachShader(p, f); 440 | glLinkProgram(p); 441 | GLint link_status; 442 | glGetProgramiv(p, GL_LINK_STATUS, &link_status); 443 | if (link_status != GL_TRUE) { 444 | if (info_log) { 445 | GLint info_len; 446 | glGetProgramiv(p, GL_INFO_LOG_LENGTH, &info_len); 447 | if (info_len > 0) { 448 | *info_log = malloc(info_len); 449 | glGetProgramInfoLog(p, info_len, NULL, *info_log); 450 | } 451 | } 452 | goto FAIL; 453 | } 454 | 455 | // Validate program. 456 | 457 | glValidateProgram(p); 458 | GLint valid_status; 459 | glGetProgramiv(p, GL_VALIDATE_STATUS, &valid_status); 460 | if (valid_status != GL_TRUE) { 461 | if (info_log) { 462 | GLint info_len; 463 | glGetProgramiv(p, GL_INFO_LOG_LENGTH, &info_len); 464 | if (info_len > 0) { 465 | *info_log = malloc(info_len); 466 | glGetProgramInfoLog(p, info_len, NULL, *info_log); 467 | } 468 | } 469 | goto FAIL; 470 | } 471 | 472 | return p; 473 | 474 | FAIL: 475 | if (p) 476 | glDeleteProgram(p); 477 | if (f) 478 | glDeleteShader(f); 479 | if (v) 480 | glDeleteShader(v); 481 | return 0; 482 | } 483 | -------------------------------------------------------------------------------- /c/libshade/prog.h: -------------------------------------------------------------------------------- 1 | #ifndef PROG_included 2 | #define PROG_included 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | typedef enum shader_type { 10 | PST_VERTEX, 11 | PST_FRAGMENT, 12 | } shader_type; 13 | 14 | typedef enum predefined { 15 | PD_RESOLUTION, 16 | PD_PLAY_TIME, 17 | PD_FRAME, 18 | PD_NOISE_SMALL, 19 | PD_NOISE_MEDIUM, 20 | 21 | PD_UNKNOWN = -999, 22 | } predefined; 23 | 24 | typedef struct shd_prog prog; 25 | 26 | extern prog *create_prog(void); 27 | extern void destroy_prog(prog *); 28 | extern bool prog_is_okay(const prog *, char **info_log); 29 | extern bool prog_attach_shader(prog *, 30 | shader_type, 31 | const char *source); 32 | extern bool prog_attach_image(prog *, 33 | const char *name, 34 | size_t width, 35 | size_t height, 36 | uint8_t *data); 37 | extern bool prog_attach_predefined(prog *, 38 | const char *name, 39 | predefined value); 40 | 41 | extern GLuint prog_instantiate(const prog *, char **info_log); 42 | extern int prog_id(const prog *); 43 | 44 | extern size_t prog_image_count(const prog *); 45 | extern const char *prog_image_name(const prog *, size_t index); 46 | extern size_t prog_image_width(const prog *, size_t index); 47 | extern size_t prog_image_height(const prog *, size_t index); 48 | extern const uint8_t *prog_image_data(const prog *, size_t index); 49 | 50 | extern size_t prog_predefined_count(const prog *); 51 | extern const char *prog_predefined_name(const prog *, size_t index); 52 | extern predefined prog_predefined_value(const prog *, size_t index); 53 | 54 | #endif /* !PROG_included */ 55 | -------------------------------------------------------------------------------- /c/libshade/queue.c: -------------------------------------------------------------------------------- 1 | #include "queue.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct queue { 8 | pthread_mutex_t lock; 9 | pthread_cond_t nonempty; 10 | pthread_cond_t nonfull; 11 | size_t size; 12 | size_t head; 13 | size_t tail; 14 | }; 15 | 16 | static void queue_init(queue *q, size_t size) 17 | { 18 | pthread_mutex_init(&q->lock, NULL); 19 | pthread_cond_init(&q->nonempty, NULL); 20 | pthread_cond_init(&q->nonfull, NULL); 21 | q->size = size; 22 | q->head = 0; 23 | q->tail = 0; 24 | } 25 | 26 | static void queue_deinit(queue *q) 27 | { 28 | pthread_mutex_destroy(&q->lock); 29 | } 30 | 31 | queue *create_queue(size_t size) 32 | { 33 | queue *q = calloc(1, sizeof *q); 34 | queue_init(q, size); 35 | return q; 36 | } 37 | 38 | void destroy_queue(queue *q) 39 | { 40 | queue_deinit(q); 41 | free(q); 42 | } 43 | 44 | static bool queue_is_empty(const queue *q) 45 | { 46 | return q->head == q->tail; 47 | } 48 | 49 | static bool queue_is_full(const queue *q) 50 | { 51 | return q->head != q->tail && (q->size + q->head - q->tail) % q->size == 0; 52 | } 53 | 54 | size_t queue_acquire_empty(queue *q) 55 | { 56 | pthread_mutex_lock(&q->lock); 57 | while (queue_is_full(q)) { 58 | pthread_cond_wait(&q->nonfull, &q->lock); 59 | } 60 | size_t index = q->tail % q->size; 61 | pthread_mutex_unlock(&q->lock); 62 | return index; 63 | } 64 | 65 | void queue_release_full(queue *q) 66 | { 67 | pthread_mutex_lock(&q->lock); 68 | q->tail = (q->tail + 1) % (2 * q->size); 69 | pthread_cond_signal(&q->nonempty); 70 | pthread_mutex_unlock(&q->lock); 71 | } 72 | 73 | size_t queue_acquire_full(queue *q) 74 | { 75 | pthread_mutex_lock(&q->lock); 76 | while (queue_is_empty(q)) { 77 | pthread_cond_wait(&q->nonempty, &q->lock); 78 | } 79 | size_t index = q->head % q->size; 80 | pthread_mutex_unlock(&q->lock); 81 | return index; 82 | } 83 | 84 | void queue_release_empty(queue *q) 85 | { 86 | pthread_mutex_lock(&q->lock); 87 | q->head = (q->head + 1) % (2 * q->size); 88 | pthread_cond_signal(&q->nonfull); 89 | pthread_mutex_unlock(&q->lock); 90 | } 91 | -------------------------------------------------------------------------------- /c/libshade/queue.h: -------------------------------------------------------------------------------- 1 | #ifndef QUEUE_included 2 | #define QUEUE_included 3 | 4 | #include 5 | 6 | typedef struct queue queue; 7 | 8 | extern queue *create_queue(size_t size); 9 | extern void destroy_queue(queue *q); 10 | 11 | extern size_t queue_acquire_empty(queue *q); 12 | extern void queue_release_full(queue *q); 13 | extern size_t queue_acquire_full(queue *q); 14 | extern void queue_release_empty(queue *q); 15 | 16 | #endif /* !QUEUE_included */ 17 | -------------------------------------------------------------------------------- /c/libshade/render.c: -------------------------------------------------------------------------------- 1 | #include "render.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "egl.h" 10 | 11 | static GLfloat vertices[] = { 12 | -1.0, -1.0, 0.0, 13 | +1.0, -1.0, 0.0, 14 | -1.0, +1.0, 0.0, 15 | +1.0, +1.0, 0.0, 16 | }; 17 | static size_t vertex_count = ((&vertices)[1] - vertices) / 3; 18 | 19 | #define NOISE_SMALL_DIM 64 20 | #define NOISE_MEDIUM_DIM 256 21 | 22 | static uint8_t noise_small_data[NOISE_SMALL_DIM * NOISE_SMALL_DIM * 4]; 23 | static uint8_t noise_medium_data[NOISE_MEDIUM_DIM * NOISE_MEDIUM_DIM * 4]; 24 | 25 | typedef struct pd_map { 26 | GLint index; 27 | predefined value; 28 | } pd_map; 29 | 30 | struct render_state { 31 | bcm_context bcm; 32 | EGL_context *egl; 33 | int prog_id; 34 | GLuint prog; 35 | GLint vert_index; 36 | struct timespec time_zero; 37 | GLint frame_counter; 38 | GLenum active_texture; 39 | size_t pd_count; 40 | pd_map *pd_map; 41 | }; 42 | 43 | render_state *render_init(const bcm_context bcm) 44 | { 45 | render_state *rs = calloc(1, sizeof *rs); 46 | 47 | rs->bcm = bcm; 48 | uint32_t bcm_surface = bcm_get_surface(bcm); 49 | uint32_t surface_width = bcm_get_surface_width(bcm); 50 | uint32_t surface_height = bcm_get_surface_height(bcm); 51 | rs->egl = init_EGL(bcm_surface, surface_width, surface_height); 52 | 53 | glViewport(0, 0, 128*6, 128); 54 | glClearColor(0.0, 0.0, 0.0, 1.0); 55 | 56 | srand(69069); // historical reasons 57 | for (size_t i = 0; i < sizeof noise_small_data; i++) 58 | noise_small_data[i] = random() % 256; 59 | for (size_t i = 0; i < sizeof noise_medium_data; i++) 60 | noise_medium_data[i] = random() % 256; 61 | 62 | return rs; 63 | } 64 | 65 | void render_deinit(render_state *rs) 66 | { 67 | glDeleteProgram(rs->prog); 68 | deinit_EGL(rs->egl); 69 | free(rs->pd_map); 70 | free(rs); 71 | } 72 | 73 | static void load_texture(render_state *rs, 74 | GLint index, 75 | size_t width, 76 | size_t height, 77 | const uint8_t *data) 78 | { 79 | if (index == -1) 80 | return; 81 | 82 | glActiveTexture(rs->active_texture++); 83 | GLuint texture; 84 | glGenTextures(1, &texture); 85 | glBindTexture(GL_TEXTURE_2D, texture); 86 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 87 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 88 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 89 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 90 | glTexImage2D(GL_TEXTURE_2D, 91 | 0, // level 92 | GL_RGBA, 93 | width, 94 | height, 95 | 0, // border, must be zero 96 | GL_RGBA, 97 | GL_UNSIGNED_BYTE, 98 | data); 99 | glGenerateMipmap(GL_TEXTURE_2D); 100 | glUniform1i(index, 0); 101 | } 102 | 103 | static void load_images(render_state *rs, const prog *pp) 104 | { 105 | rs->active_texture = GL_TEXTURE0; 106 | size_t im_count = prog_image_count(pp); 107 | for (size_t i = 0; i < im_count; i++) { 108 | const char *name = prog_image_name(pp, i); 109 | size_t width = prog_image_width(pp, i); 110 | size_t height = prog_image_height(pp, i); 111 | const uint8_t *data = prog_image_data(pp, i); 112 | GLint index = glGetUniformLocation(rs->prog, name); 113 | load_texture(rs, index, width, height, data); 114 | } 115 | } 116 | 117 | static void update_predefineds(render_state *rs, const prog *pp, bool new_prog) 118 | { 119 | size_t pd_count = prog_predefined_count(pp); 120 | if (new_prog) { 121 | free(rs->pd_map); 122 | rs->pd_map = calloc(pd_count, sizeof *rs->pd_map); 123 | rs->pd_count = pd_count; 124 | for (size_t i = 0; i < pd_count; i++) { 125 | const char *name = prog_predefined_name(pp, i); 126 | predefined value = prog_predefined_value(pp, i); 127 | GLint index = glGetUniformLocation(rs->prog, name); 128 | rs->pd_map[i].index = index; 129 | rs->pd_map[i].value = value; 130 | switch (value) { 131 | 132 | case PD_RESOLUTION: 133 | glUniform3f(index, 134 | (GLfloat)768.0, // XXX 135 | (GLfloat)128.0, // XXX 136 | (GLfloat)1.0); 137 | break; 138 | 139 | case PD_PLAY_TIME: 140 | clock_gettime(CLOCK_MONOTONIC, &rs->time_zero); 141 | glUniform1f(index, 0.0); 142 | break; 143 | 144 | case PD_FRAME: 145 | rs->frame_counter = 0; 146 | break; 147 | 148 | case PD_NOISE_SMALL: 149 | load_texture(rs, 150 | index, 151 | NOISE_SMALL_DIM, 152 | NOISE_SMALL_DIM, 153 | noise_small_data); 154 | break; 155 | 156 | case PD_NOISE_MEDIUM: 157 | load_texture(rs, 158 | index, 159 | NOISE_MEDIUM_DIM, 160 | NOISE_MEDIUM_DIM, 161 | noise_medium_data); 162 | break; 163 | 164 | default: 165 | break; 166 | } 167 | } 168 | } 169 | for (size_t i = 0; i < rs->pd_count; i++) { 170 | GLint index = rs->pd_map[i].index; 171 | switch (rs->pd_map[i].value) { 172 | 173 | case PD_PLAY_TIME: 174 | if (!new_prog) { 175 | struct timespec now; 176 | clock_gettime(CLOCK_MONOTONIC, &now); 177 | GLfloat t = (now.tv_nsec - rs->time_zero.tv_nsec) / 1.0e9; 178 | t += now.tv_sec - rs->time_zero.tv_sec; 179 | glUniform1f(index, t); 180 | } 181 | break; 182 | 183 | case PD_FRAME: 184 | glUniform1i(index, rs->frame_counter); 185 | rs->frame_counter++; 186 | break; 187 | 188 | default: 189 | break; 190 | } 191 | } 192 | } 193 | 194 | #define CHECK_ERROR \ 195 | ({ \ 196 | GLenum err = glGetError(); \ 197 | if (err) \ 198 | fprintf(stderr, "%s:%d: GL error 0x%04x\n", \ 199 | __FILE__, __LINE__, err); \ 200 | }) 201 | 202 | 203 | void render_frame(render_state *rs, const prog *pp) 204 | { 205 | bool new_prog = rs->prog_id != prog_id(pp); 206 | if (new_prog) { 207 | rs->prog_id = prog_id(pp); 208 | glDeleteProgram(rs->prog); 209 | rs->prog = prog_instantiate((prog *)pp, NULL); 210 | CHECK_ERROR; 211 | glUseProgram(rs->prog); 212 | CHECK_ERROR; 213 | rs->vert_index = glGetAttribLocation(rs->prog, "vert"); 214 | CHECK_ERROR; 215 | glVertexAttribPointer(rs->vert_index, 216 | 3, 217 | GL_FLOAT, 218 | GL_FALSE, 219 | 0, 220 | vertices); 221 | CHECK_ERROR; 222 | glEnableVertexAttribArray(rs->vert_index); 223 | CHECK_ERROR; 224 | load_images(rs, pp); 225 | CHECK_ERROR; 226 | } 227 | 228 | CHECK_ERROR; 229 | update_predefineds(rs, pp, new_prog); 230 | CHECK_ERROR; 231 | 232 | glClear(GL_COLOR_BUFFER_BIT); 233 | CHECK_ERROR; 234 | glDrawArrays(GL_TRIANGLE_STRIP, rs->vert_index, vertex_count); 235 | CHECK_ERROR; 236 | 237 | EGL_swap_buffers(rs->egl); 238 | } 239 | -------------------------------------------------------------------------------- /c/libshade/render.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDER_included 2 | #define RENDER_included 3 | 4 | #include "bcm.h" 5 | #include "prog.h" 6 | 7 | typedef struct render_state render_state; 8 | 9 | extern render_state *render_init(const bcm_context); 10 | extern void render_deinit(render_state *); 11 | extern void render_frame(render_state *, const prog *); 12 | 13 | #endif /* !RENDER_included */ 14 | -------------------------------------------------------------------------------- /c/libshade/shade.c: -------------------------------------------------------------------------------- 1 | #include "shade.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "bcm.h" 7 | #include "egl.h" 8 | #include "exec.h" 9 | #include "prog.h" 10 | 11 | #define EXPORT __attribute__((visibility("default"))) 12 | 13 | EXPORT const int SHD_SHADER_VERTEX_VALUE = SHD_SHADER_VERTEX; 14 | EXPORT const int SHD_SHADER_FRAGMENT_VALUE = SHD_SHADER_FRAGMENT; 15 | 16 | EXPORT const int SHD_PREDEFINED_RESOLUTION_VALUE = SHD_PREDEFINED_RESOLUTION; 17 | EXPORT const int SHD_PREDEFINED_PLAY_TIME_VALUE = SHD_PREDEFINED_PLAY_TIME; 18 | EXPORT const int SHD_PREDEFINED_RENDER_TIME_VALUE = SHD_PREDEFINED_RENDER_TIME; 19 | EXPORT const int SHD_PREDEFINED_FRAME_VALUE = SHD_PREDEFINED_FRAME; 20 | EXPORT const int SHD_PREDEFINED_NOISE_SMALL_VALUE = SHD_PREDEFINED_NOISE_SMALL; 21 | EXPORT const int SHD_PREDEFINED_NOISE_MEDIUM_VALUE = 22 | SHD_PREDEFINED_NOISE_MEDIUM; 23 | EXPORT const int SHD_PREDEFINED_BACK_BUFFER_VALUE = SHD_PREDEFINED_BACK_BUFFER; 24 | EXPORT const int SHD_PREDEFINED_IMU_VALUE = SHD_PREDEFINED_IMU; 25 | 26 | static bcm_context *the_bcm; 27 | static EGL_context *the_EGL; 28 | static LEDs_context *the_LEDs; 29 | static exec *the_exec; 30 | static char *the_info_log; 31 | 32 | // Should pass in sizes? 33 | 34 | EXPORT void shd_init(int LEDs_width, int LEDs_height) 35 | { 36 | the_bcm = init_bcm(); 37 | uint32_t bcm_surface = bcm_get_surface(the_bcm); 38 | uint32_t surface_width = bcm_get_surface_width(the_bcm); 39 | uint32_t surface_height = bcm_get_surface_height(the_bcm); 40 | uint32_t pixels_width = surface_width / 2; 41 | uint32_t pixels_height = surface_height / 2; 42 | uint32_t pixels_offset = (pixels_height - LEDs_height) * pixels_width; 43 | the_EGL = init_EGL(bcm_surface, surface_width, surface_height); 44 | the_LEDs = init_LEDs(LEDs_width, 45 | LEDs_height, 46 | pixels_width, 47 | pixels_height, 48 | pixels_offset); 49 | the_exec = create_exec(the_bcm, the_LEDs); 50 | } 51 | 52 | EXPORT void shd_deinit(void) 53 | { 54 | // XXX destroy all programs 55 | if (the_exec) { 56 | exec_stop(the_exec); 57 | destroy_exec(the_exec); 58 | the_exec = NULL; 59 | } 60 | if (the_LEDs) { 61 | deinit_LEDs(the_LEDs); 62 | the_LEDs = NULL; 63 | } 64 | if (the_EGL) { 65 | deinit_EGL(the_EGL); 66 | the_EGL = NULL; 67 | } 68 | if (the_bcm) { 69 | deinit_bcm(the_bcm); 70 | the_bcm = NULL; 71 | } 72 | free(the_info_log); 73 | the_info_log = NULL; 74 | } 75 | 76 | EXPORT void shd_start(void) 77 | { 78 | exec_start(the_exec); 79 | } 80 | 81 | EXPORT void shd_stop(void) 82 | { 83 | exec_stop(the_exec); 84 | } 85 | 86 | EXPORT double shd_fps(void) 87 | { 88 | return exec_fps(the_exec); 89 | } 90 | 91 | EXPORT void shd_use_prog(shd_prog *pp) 92 | { 93 | exec_use_prog(the_exec, pp); 94 | } 95 | 96 | EXPORT shd_prog *shd_create_prog(void) 97 | { 98 | return create_prog(); 99 | } 100 | 101 | EXPORT void shd_destroy_prog(shd_prog *prog) 102 | { 103 | destroy_prog(prog); 104 | } 105 | 106 | EXPORT bool shd_prog_is_okay(const shd_prog *prog, char **info_log) 107 | { 108 | free(the_info_log); 109 | the_info_log = NULL; 110 | bool ok = prog_is_okay(prog, &the_info_log); 111 | if (!ok && info_log) 112 | *info_log = the_info_log; 113 | return ok; 114 | } 115 | 116 | EXPORT bool shd_prog_attach_shader(shd_prog *prog, 117 | shd_shader_type type, 118 | const char *source) 119 | { 120 | shader_type ptype; 121 | switch (type) { 122 | 123 | case SHD_SHADER_VERTEX: 124 | ptype = PST_VERTEX; 125 | break; 126 | 127 | case SHD_SHADER_FRAGMENT: 128 | ptype = PST_FRAGMENT; 129 | break; 130 | 131 | default: 132 | return false; 133 | } 134 | return prog_attach_shader(prog, ptype, source); 135 | } 136 | 137 | EXPORT bool shd_prog_attach_image(shd_prog *pp, 138 | const char *name, 139 | size_t width, 140 | size_t height, 141 | uint8_t *data) 142 | { 143 | return prog_attach_image(pp, name, width, height, data); 144 | } 145 | 146 | EXPORT bool shd_prog_attach_predefined(shd_prog *pp, 147 | const char *name, 148 | shd_predefined predef) 149 | { 150 | predefined pd; 151 | switch (predef) { 152 | 153 | case SHD_PREDEFINED_RESOLUTION: 154 | pd = PD_RESOLUTION; 155 | break; 156 | 157 | case SHD_PREDEFINED_PLAY_TIME: 158 | pd = PD_PLAY_TIME; 159 | break; 160 | 161 | case SHD_PREDEFINED_FRAME: 162 | pd = PD_FRAME; 163 | break; 164 | 165 | case SHD_PREDEFINED_NOISE_SMALL: 166 | pd = PD_NOISE_SMALL; 167 | break; 168 | 169 | case SHD_PREDEFINED_NOISE_MEDIUM: 170 | pd = PD_NOISE_MEDIUM; 171 | break; 172 | 173 | default: 174 | return false; 175 | } 176 | return prog_attach_predefined(pp, name, pd); 177 | } 178 | -------------------------------------------------------------------------------- /c/libshade/shade.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADE_included 2 | #define SHADE_included 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum shd_shader_type { 9 | SHD_SHADER_VERTEX, 10 | SHD_SHADER_FRAGMENT, 11 | } shd_shader_type; 12 | 13 | typedef enum shd_predefined { 14 | SHD_PREDEFINED_RESOLUTION, 15 | SHD_PREDEFINED_PLAY_TIME, 16 | SHD_PREDEFINED_RENDER_TIME, 17 | SHD_PREDEFINED_FRAME, 18 | SHD_PREDEFINED_NOISE_SMALL, 19 | SHD_PREDEFINED_NOISE_MEDIUM, 20 | SHD_PREDEFINED_BACK_BUFFER, 21 | SHD_PREDEFINED_IMU, 22 | } shd_predefined; 23 | 24 | // These are integer constants matching the enum values above. 25 | // Python can't access the enum values directly. 26 | extern const int SHD_SHADER_VERTEX_VALUE; 27 | extern const int SHD_SHADER_FRAGMENT_VALUE; 28 | 29 | extern const int SHD_PREDEFINED_RESOLUTION_VALUE; 30 | extern const int SHD_PREDEFINED_PLAY_TIME_VALUE; 31 | extern const int SHD_PREDEFINED_RENDER_TIME_VALUE; 32 | extern const int SHD_PREDEFINED_FRAME_VALUE; 33 | extern const int SHD_PREDEFINED_NOISE_SMALL_VALUE; 34 | extern const int SHD_PREDEFINED_NOISE_MEDIUM_VALUE; 35 | extern const int SHD_PREDEFINED_BACK_BUFFER_VALUE; 36 | extern const int SHD_PREDEFINED_IMU_VALUE; 37 | 38 | typedef struct shd_prog shd_prog; 39 | 40 | extern void shd_init(int LEDs_width, int LEDs_height); 41 | extern void shd_deinit(void); 42 | 43 | extern void shd_start(void); 44 | extern void shd_stop(void); 45 | extern double shd_fps(void); 46 | extern void shd_use_prog(shd_prog *); 47 | 48 | extern shd_prog *shd_create_prog(void); 49 | extern void shd_destroy_prog(shd_prog *); 50 | extern bool shd_prog_is_okay(const shd_prog *, 51 | char **info_log); 52 | extern bool shd_prog_attach_shader(shd_prog *, 53 | shd_shader_type type, 54 | const char *source); 55 | extern bool shd_prog_attach_image(shd_prog *, 56 | const char *name, 57 | size_t width, 58 | size_t height, 59 | uint8_t *data); 60 | extern bool shd_prog_attach_predefined(shd_prog *, 61 | const char *name, 62 | shd_predefined); 63 | 64 | #endif /* !SHADE_included */ 65 | -------------------------------------------------------------------------------- /c/test/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Vars.make 2 | include ../Rules.make 3 | 4 | LIBSHADE_DIR := ../libshade 5 | LIBSHADE_A := $(LIBSHADE_DIR)/libshade.a 6 | LIBSHADE_SO := $(LIBSHADE_DIR)/libshade.so 7 | 8 | CPPFLAGS += -I$(LIBSHADE_DIR) 9 | 10 | ptest_CFILES := ptest.c $(LIBSHADE_DIR)/queue.c 11 | ptest_OFILES := $(ptest_CFILES:.c=.o) 12 | ptest_LDLIBS := -lpthread 13 | 14 | TARGETS := ptest ltest-static ltest-dynamic 15 | 16 | build: $(TARGETS) 17 | 18 | ptest: LDLIBS := $(ptest_LDLIBS) 19 | ptest: $(ptest_OFILES) 20 | 21 | ltest-static: LDLIBS += $(LIBSHADE_A) 22 | ltest-static: ltest.o $(LIBSHADE_A) 23 | $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ 24 | 25 | ltest-dynamic: LDLIBS += $(LIBSHADE_SO) 26 | ltest-dynamic: ltest.o $(LIBSHADE_SO) 27 | $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ 28 | 29 | test: build 30 | ./ptest 31 | ./ltest-static 32 | ./ltest-dynamic 33 | 34 | clean: 35 | rm -f *.o $(TARGETS) 36 | 37 | install: build 38 | 39 | uninstall: 40 | -------------------------------------------------------------------------------- /c/test/ltest.c: -------------------------------------------------------------------------------- 1 | // Test library. 2 | 3 | #include 4 | #include 5 | 6 | #include "shade.h" 7 | 8 | static const int LEDS_WIDTH = 6 * 64; 9 | static const int LEDS_HEIGHT = 64; 10 | 11 | static const char vertex_shader_source[] = 12 | "attribute vec3 vert;\n" 13 | "\n" 14 | "void main(void) {\n" 15 | " gl_Position = vec4(vert, 1.0);\n" 16 | "}\n" 17 | ; 18 | 19 | static const char frag_shader_source[] = 20 | "uniform vec3 iResolution;\n" 21 | "uniform float iTime;\n" 22 | "uniform float iTimeDelta;\n" 23 | "uniform float iFrame;\n" 24 | "uniform float iChannelTime[4];\n" 25 | "uniform vec4 iMouse;\n" 26 | "uniform vec4 iDate;\n" 27 | "uniform float iSampleRate;\n" 28 | "uniform vec3 iChannelResolution[4];\n" 29 | "const float PI = 3.141592;\n" 30 | "const float EPSILON = 0.0001;\n" 31 | "\n" 32 | "// Maps 2D output image space to 3D cube space.\n" 33 | "//\n" 34 | "// The returned coordinates are in the range of (-.5, .5).\n" 35 | "vec3 cube_map_to_3d(vec2 pos) {\n" 36 | " vec3 p = vec3(0.0);\n" 37 | " if (pos.x < 128.0) {\n" 38 | " // top\n" 39 | " p = vec3(1.0 - pos.y / 128.0,\n" 40 | " 1.0,\n" 41 | " pos.x / 128.0);\n" 42 | " } else if (pos.x < 256.0) {\n" 43 | " // back\n" 44 | " p = vec3(1.0 - pos.y / 128.0,\n" 45 | " 1.0 - (pos.x - 128.0) / 128.0,\n" 46 | " 1.0);\n" 47 | " } else if (pos.x < 384.0) {\n" 48 | " // bottom\n" 49 | " p = vec3(1.0 - pos.y / 128.0,\n" 50 | " 0.0,\n" 51 | " 1.0 - (pos.x - 256.0) / 128.0);\n" 52 | " } else if (pos.x < 512.0) {\n" 53 | " // right\n" 54 | " p = vec3(1.0,\n" 55 | " 1.0 - pos.y / 128.0,\n" 56 | " 1.0 - (pos.x - 384.0) / 128.0);\n" 57 | " } else if (pos.x < 640.0) {\n" 58 | " // front\n" 59 | " p = vec3(1.0 - (pos.x - 512.0) / 128.0,\n" 60 | " 1.0 - pos.y / 128.0,\n" 61 | " 0.0);\n" 62 | " } else if (pos.x < 768.0) {\n" 63 | " // left\n" 64 | " p = vec3(0,\n" 65 | " 1.0 - pos.y / 128.0,\n" 66 | " (pos.x - 640.0) / 128.0);\n" 67 | " }\n" 68 | " return p - 0.5;\n" 69 | "}\n" 70 | "\n" 71 | " void mainCube(out vec4 fragColor, in vec3 fragCoord) {\n" 72 | " fragColor.rgb = fragCoord.xyz + .5;\n" 73 | "}\n" 74 | "\n" 75 | "#ifndef _EMULATOR\n" 76 | "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n" 77 | " mainCube(fragColor, cube_map_to_3d(fragCoord));\n" 78 | "}\n" 79 | "#endif\n" 80 | "\n" 81 | "void main() {\n" 82 | " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n" 83 | " mainImage(gl_FragColor, gl_FragCoord.xy);\n" 84 | " gl_FragColor.a = 1.0;\n" 85 | "}\n" 86 | ; 87 | 88 | int main() 89 | { 90 | shd_init(LEDS_WIDTH, LEDS_HEIGHT); 91 | shd_prog *prog = shd_create_prog(); 92 | shd_prog_attach_shader(prog, SHD_SHADER_VERTEX, vertex_shader_source); 93 | shd_prog_attach_shader(prog, SHD_SHADER_FRAGMENT, frag_shader_source); 94 | char *info_log = NULL; 95 | if (!shd_prog_is_okay(prog, &info_log)) { 96 | fprintf(stderr, "info: %s\n", info_log); 97 | return 1; 98 | } 99 | shd_use_prog(prog); 100 | 101 | shd_start(); 102 | sleep(1); 103 | shd_stop(); 104 | shd_destroy_prog(prog); 105 | shd_deinit(); 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /c/test/ptest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "queue.h" 5 | 6 | typedef struct queues { 7 | queue *a; 8 | queue *b; 9 | char b0[10]; 10 | char b1[10]; 11 | } queues; 12 | 13 | static void *produce(void *user_data) 14 | { 15 | queues *qs = user_data; 16 | for (const char *src = "_Hello, World!\n"; *src; src++) { 17 | size_t i = queue_acquire_empty(qs->a); 18 | qs->b0[i] = src[1]; 19 | queue_release_full(qs->a); 20 | } 21 | return NULL; 22 | } 23 | 24 | static void *mediate(void *user_data) 25 | { 26 | queues *qs = user_data; 27 | while (1) { 28 | size_t i0 = queue_acquire_full(qs->a); 29 | char c = qs->b0[i0]; 30 | queue_release_empty(qs->a); 31 | size_t i1 = queue_acquire_empty(qs->b); 32 | qs->b1[i1] = c; 33 | queue_release_full(qs->b); 34 | if (c == 0) 35 | break; 36 | } 37 | return NULL; 38 | } 39 | 40 | static void *consume(void *user_data) 41 | { 42 | queues *qs = user_data; 43 | while (1) { 44 | size_t index = queue_acquire_full(qs->b); 45 | char c = qs->b1[index]; 46 | queue_release_empty(qs->b); 47 | if (c == '\0') 48 | break; 49 | putchar(c); 50 | fflush(stdout); 51 | } 52 | return NULL; 53 | } 54 | 55 | int main(void) 56 | { 57 | queues qs; 58 | qs.a = create_queue(4); 59 | qs.b = create_queue(3); 60 | 61 | pthread_t producer, middle, consumer; 62 | 63 | pthread_create(&producer, NULL, produce, &qs); 64 | pthread_create(&middle, NULL, mediate, &qs); 65 | pthread_create(&consumer, NULL, consume, &qs); 66 | pthread_join(producer, NULL); 67 | pthread_join(middle, NULL); 68 | pthread_join(consumer, NULL); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /python/Makefile: -------------------------------------------------------------------------------- 1 | include ../Vars.make 2 | 3 | TARGETS := shaderbox shade/__init__.py 4 | 5 | build: $(TARGETS) 6 | 7 | test: 8 | ./pytest 9 | 10 | clean: 11 | rm -f *.pyc 12 | rm -rf __pycache__ */__pycache__ build dist *.egg-info 13 | 14 | install: build 15 | # Pip refuses to install if the package is in the current directory. 16 | cd /tmp && \ 17 | $(PYTHON) -m pip install --prefix=$(PREFIX) --upgrade $(CURDIR) 18 | $(INSTALL) shaderbox $(PREFIX)/bin 19 | 20 | uninstall: 21 | # Pip refuses to deinstall if the package is in the current directory. 22 | cd /tmp && $(PYTHON) -m pip uninstall $(CURDIR) 23 | rm -f $(PREFIX)/bin/shaderbox 24 | -------------------------------------------------------------------------------- /python/pytest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import ctypes 4 | from ctypes import byref, c_char, c_char_p, c_double, c_int, c_void_p, POINTER 5 | import numpy as np 6 | import PIL.Image 7 | import time 8 | 9 | vertex_shader_source = ''' 10 | attribute vec3 vert; 11 | 12 | void main(void) { 13 | gl_Position = vec4(vert, 1.0); 14 | } 15 | ''' 16 | 17 | fragment_shader_source = ''' 18 | uniform float iTime; 19 | uniform vec3 iResolution; 20 | uniform sampler2D noise_small; 21 | 22 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 23 | { 24 | // Normalized pixel coordinates (from 0 to 1) 25 | vec2 uv = fragCoord/iResolution.xy; 26 | vec2 uv6 = uv * vec2(6.0, 1.0); 27 | 28 | // Time varying pixel color 29 | vec3 col = 0.5 + 0.5*cos(iTime + uv6.xyx+vec3(0,2,4)); 30 | vec3 c2 = texture2D(noise_small, uv6 + iTime * 0.1).rgb; 31 | c2 = c2 * c2; 32 | col *= c2; 33 | 34 | // Output to screen 35 | fragColor = vec4(col, 1.0); 36 | } 37 | 38 | void main() { 39 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 40 | mainImage(gl_FragColor, gl_FragCoord.xy); 41 | gl_FragColor.a = 1.0; 42 | } 43 | ''' 44 | 45 | 46 | def load_image(path): 47 | img = PIL.Image.open(path) 48 | mode = img.mode 49 | if mode != 'RGBA': 50 | exit('{} has unsupported mode {}'.format(path, mode)) 51 | a = np.array(img) 52 | return (img.width, img.height, a.tobytes()) 53 | 54 | shade = ctypes.cdll.LoadLibrary('./libshade.so') 55 | 56 | use_prog = shade.shd_use_prog 57 | use_prog.argtypes = (c_void_p, ) 58 | 59 | create_prog = shade.shd_create_prog 60 | create_prog.restype = c_void_p 61 | 62 | destroy_prog = shade.shd_destroy_prog 63 | destroy_prog.argtypes = (c_void_p, ) 64 | 65 | prog_is_okay = shade.shd_prog_is_okay 66 | prog_is_okay.argtypes = (c_void_p, POINTER(c_char_p)) 67 | 68 | prog_attach_shader = shade.shd_prog_attach_shader 69 | prog_attach_shader.argtypes = (c_void_p, c_int, c_char_p) 70 | 71 | prog_attach_image = shade.shd_prog_attach_image 72 | prog_attach_image.argtypes = (c_void_p, c_char_p, c_int, c_int, c_char_p) 73 | 74 | prog_attach_predefined = shade.shd_prog_attach_predefined 75 | prog_attach_predefined.argtypes = (c_void_p, c_char_p, c_int) 76 | 77 | fps = shade.shd_fps 78 | fps.argtypes = () 79 | fps.restype = c_double 80 | 81 | VERT = c_int.in_dll(shade, 'SHD_SHADER_VERTEX_VALUE') 82 | FRAG = c_int.in_dll(shade, 'SHD_SHADER_FRAGMENT_VALUE') 83 | 84 | RESOLUTION = c_int.in_dll(shade, 'SHD_PREDEFINED_RESOLUTION_VALUE') 85 | PLAY_TIME = c_int.in_dll(shade, 'SHD_PREDEFINED_PLAY_TIME_VALUE') 86 | NOISE_SMALL = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_SMALL_VALUE') 87 | NOISE_MEDIUM = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_MEDIUM_VALUE') 88 | 89 | LEDS_WIDTH = 6 * 64 90 | LEDS_HEIGHT = 64 91 | 92 | img = load_image('test.png') 93 | 94 | shade.shd_init(LEDS_WIDTH, LEDS_HEIGHT) 95 | 96 | prog = create_prog() 97 | prog_attach_shader(prog, VERT, vertex_shader_source.encode('ascii')) 98 | prog_attach_shader(prog, FRAG, fragment_shader_source.encode('ascii')) 99 | prog_attach_predefined(prog, b'iResolution', RESOLUTION) 100 | prog_attach_predefined(prog, b'iTime', PLAY_TIME) 101 | prog_attach_image(prog, b'noise_small', *img) 102 | info_log = c_char_p(); 103 | if not shade.shd_prog_is_okay(prog, byref(info_log)): 104 | print(info_log.value.decode('ascii')) 105 | exit() 106 | shade.shd_use_prog(prog) 107 | 108 | shade.shd_start() 109 | 110 | for i in range(3): 111 | try: 112 | time.sleep(1) 113 | print('FPS: {:.3}'.format(fps())) 114 | except KeyboardInterrupt: 115 | print() 116 | break 117 | 118 | shade.shd_stop() 119 | destroy_prog(prog) 120 | shade.shd_deinit() 121 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | try: 4 | with open('../README.md') as f: 5 | long_description = f.read() 6 | except FileNotFoundError: 7 | # Pip runs this from some other directory and I don't care. 8 | long_description = '???' 9 | 10 | setuptools.setup( 11 | name='shade-kbob', 12 | version='0.0.1', 13 | author_email='kbob@jogger-egg.com', 14 | description='GL shaders for LED cubes', 15 | long_description=long_description, 16 | # long_description_content_type='text/markdown', 17 | url='https://github.com/kbob/shaderboy', 18 | packages=setuptools.find_packages(), 19 | python_requires='>=3.5', 20 | ) 21 | -------------------------------------------------------------------------------- /python/shade/__init__.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from ctypes import byref, c_bool, c_char, c_char_p, c_double, c_int 3 | from ctypes import c_void_p, POINTER 4 | from enum import Enum 5 | 6 | 7 | __all__ = [ 8 | 'ShaderType', 9 | 'Predefined', 10 | 'ProgError', 11 | 'Prog', 12 | 'init', 13 | 'deinit', 14 | 'start', 15 | 'stop', 16 | 'fps', 17 | ] 18 | 19 | 20 | try: 21 | libshade = ctypes.cdll.LoadLibrary('libshade.so') 22 | except OSError: 23 | libshade = ctypes.cdll.LoadLibrary('./libshade.so') 24 | 25 | 26 | def def_enum(name, prefix, members, suffix): 27 | 28 | class Subclass(Enum): 29 | @classmethod 30 | def from_param(cls, obj): 31 | return obj.value 32 | 33 | def c_pair(mem): 34 | c_name = prefix + mem + suffix 35 | c_var = c_int.in_dll(libshade, c_name) 36 | c_value = c_var.value 37 | return (mem, c_value) 38 | members = (c_pair(name) for name in members.split()) 39 | enum = Subclass(name, members) 40 | globals()[name] = enum 41 | 42 | 43 | def_enum('ShaderType', 'SHD_SHADER_', 'VERTEX FRAGMENT', '_VALUE') 44 | 45 | def_enum('Predefined', 46 | 'SHD_PREDEFINED_', 47 | 'RESOLUTION PLAY_TIME RENDER_TIME FRAME ' 48 | 'NOISE_SMALL NOISE_MEDIUM BACK_BUFFER IMU', 49 | '_VALUE') 50 | 51 | 52 | class ProgError(Exception): 53 | pass 54 | 55 | 56 | class Prog: 57 | 58 | def __init__(self): 59 | self.c_prog = create_prog() 60 | 61 | def close(self): 62 | destroy_prog(self.c_prog) 63 | 64 | def attach_shader(self, stype, source, encoding='utf-8'): 65 | return prog_attach_shader(self.c_prog, 66 | stype, 67 | source.encode(encoding)) 68 | 69 | def attach_image(self, name, width, height, data): 70 | return prog_attach_image(self.c_prog, 71 | name.encode('ascii'), 72 | width, height, 73 | data) 74 | 75 | def attach_predefined(self, name, predefined): 76 | return prog_attach_predefined(self.c_prog, 77 | name.encode('ascii'), 78 | predefined) 79 | 80 | def check_okay(self): 81 | info_log = c_char_p() 82 | ok = prog_is_okay(self.c_prog, byref(info_log)) 83 | if not ok: 84 | raise ProgError(info_log.value.decode('utf-8')) 85 | 86 | def make_current(self): 87 | use_prog(self.c_prog) 88 | 89 | 90 | def def_fun(name, restype, argtypes): 91 | fun = libshade['shd_' + name] 92 | fun.restype = restype 93 | fun.argtypes = argtypes 94 | globals()[name] = fun 95 | 96 | def_fun('init', None, (c_int, c_int)) 97 | def_fun('deinit', None, ()) 98 | 99 | def_fun('start', None, ()) 100 | def_fun('stop', None, ()) 101 | def_fun('fps', c_double, ()) 102 | def_fun('use_prog', None, (c_void_p, )) 103 | 104 | def_fun('create_prog', c_void_p, ()) 105 | def_fun('destroy_prog', None, (c_void_p, )); 106 | def_fun('prog_is_okay', c_bool, (c_void_p, POINTER(c_char_p))) 107 | def_fun('prog_attach_shader', c_bool, (c_void_p, ShaderType, c_char_p)) 108 | def_fun('prog_attach_image', 109 | c_bool, 110 | (c_void_p, c_char_p, c_int, c_int, c_char_p)) 111 | def_fun('prog_attach_predefined', c_bool, (c_void_p, c_char_p, Predefined)) 112 | -------------------------------------------------------------------------------- /python/shaderbox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from argparse import ArgumentParser 4 | from collections import namedtuple 5 | import io 6 | import os 7 | from pathlib import Path 8 | import re 9 | import signal 10 | import sys 11 | import time 12 | 13 | import numpy as np 14 | import PIL.Image 15 | 16 | import shade 17 | from shade import ShaderType, Predefined, Prog 18 | 19 | LEDS_WIDTH = 384 20 | LEDS_HEIGHT = 64 21 | 22 | vertex_shader_source = ''' 23 | attribute vec3 vert; 24 | 25 | void main(void) { 26 | gl_Position = vec4(vert, 1.0); 27 | } 28 | '''.replace('\n ', '\n') 29 | 30 | image_main_source = ''' 31 | void main() { 32 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 33 | mainImage(gl_FragColor, gl_FragCoord.xy); 34 | gl_FragColor.a = 1.0; 35 | } 36 | '''.replace('\n ', '\n') 37 | 38 | cube_main_source = ''' 39 | // Maps 2D output image space to 3D cube space. 40 | // 41 | // The returned coordinates are in the range of (-.5, .5). 42 | vec3 cube_map_to_3d(vec2 pos) { 43 | vec3 p = vec3(0.0); 44 | if (pos.x < 128.0) { 45 | // top 46 | p = vec3(1.0 - pos.y / 128.0, 47 | 1.0, 48 | pos.x / 128.0); 49 | } else if (pos.x < 256.0) { 50 | // back 51 | p = vec3(1.0 - pos.y / 128.0, 52 | 1.0 - (pos.x - 128.0) / 128.0, 53 | 1.0); 54 | } else if (pos.x < 384.0) { 55 | // bottom 56 | p = vec3(1.0 - pos.y / 128.0, 57 | 0.0, 58 | 1.0 - (pos.x - 256.0) / 128.0); 59 | } else if (pos.x < 512.0) { 60 | // right 61 | p = vec3(1.0, 62 | 1.0 - pos.y / 128.0, 63 | 1.0 - (pos.x - 384.0) / 128.0); 64 | } else if (pos.x < 640.0) { 65 | // front 66 | p = vec3(1.0 - (pos.x - 512.0) / 128.0, 67 | 1.0 - pos.y / 128.0, 68 | 0.0); 69 | } else if (pos.x < 768.0) { 70 | // left 71 | p = vec3(0, 72 | 1.0 - pos.y / 128.0, 73 | (pos.x - 640.0) / 128.0); 74 | } 75 | return p - 0.5; 76 | } 77 | 78 | #ifndef _EMULATOR 79 | void mainImage(out vec4 fragColor, in vec2 fragCoord) { 80 | mainCube(fragColor, cube_map_to_3d(fragCoord)); 81 | } 82 | #endif 83 | '''.replace('\n ', '\n') 84 | 85 | class PredefinedVar(namedtuple('PredefinedVar', 'name predef type size')): 86 | """Variable that environment predefines""" 87 | 88 | class ImageInfo(namedtuple('ImageInfo', 'var file spath')): 89 | """information to load an image""" 90 | 91 | class PredefInfo(namedtuple('PredefInfo', 'var predef')): 92 | """information to load a predefined variable""" 93 | 94 | 95 | predefined_vars = [ 96 | PredefinedVar('iResolution', Predefined.RESOLUTION, 'vec3', 0), 97 | PredefinedVar('iTime', Predefined.PLAY_TIME, 'float', 0), 98 | # PredefinedVar('iTimeDelta', Predefined.RENDER_TIME, 'float', 0), 99 | PredefinedVar('iFrame', Predefined.FRAME, 'int', 0), 100 | # PredefinedVar('iChannelTime', Predefined.CHANNEL_TIME, 'float', 4), 101 | # PredefinedVar('iMouse', Predefined.MOUSE, 'vec4', 0), 102 | # PredefinedVar('iDate', Predefined.DATE, 'vec4', 0), 103 | # PredefinedVar('iSampleRate', Predefined.SAMPLE_RATE, 'float', 0), 104 | # PredefinedVar('iChannelResolution', 105 | # Predefined.CHANNEL_RESOLUTION, 106 | # 'vec3', 4), 107 | ] 108 | 109 | 110 | # I like to precompile my regular expressions and prebind the desired 111 | # method. 112 | 113 | shebang_pat = r'#!' 114 | pragma_pat = r'\s*#\s*pragma\s+' 115 | ident_pat = r'[A-Za-z+]\w*' 116 | pragma_use_pat = pragma_pat + 'use\s*"(?P.*)"$' 117 | pragma_map_pat = pragma_pat + 'map\s+(?P' + ident_pat + r')\s*=\s*' 118 | pragma_image_pat = pragma_map_pat + 'image:(?P.*?)\s*$' 119 | pragma_builtin_pat = pragma_map_pat + 'builtin:(?P.*?)\s*$' 120 | pragma_perip_pat = pragma_map_pat + 'perip_map4:.*$' 121 | 122 | match_shebang = re.compile(shebang_pat).match 123 | match_pragma = re.compile(pragma_pat).match 124 | match_use = re.compile(pragma_use_pat).match 125 | match_map = re.compile(pragma_map_pat).match 126 | match_image = re.compile(pragma_image_pat).match 127 | match_builtin = re.compile(pragma_builtin_pat).match 128 | match_perip = re.compile(pragma_perip_pat).match 129 | findall_idents = re.compile(ident_pat).findall 130 | 131 | 132 | class Preprocessor: 133 | 134 | def __init__(self): 135 | self.images = [] 136 | self.predefs = [] 137 | 138 | def process(self, file): 139 | spath = Path() 140 | src = sys.stdin 141 | if file: 142 | spath = Path(file) 143 | src = spath.open() 144 | self.out = io.StringIO() 145 | self.seen = set() 146 | self._prep_stream(src, spath) 147 | self._post_preprocess() 148 | return (namedtuple('Source', 'source images predefs') 149 | (self.out.getvalue(), self.images, self.predefs)) 150 | 151 | def _prep_stream(self, src, spath): 152 | abs = spath.resolve() 153 | if abs in self.seen: 154 | return 155 | self.seen.add(abs) 156 | for (lineno, line) in enumerate(src, 1): 157 | if lineno == 1 and match_shebang(line): 158 | self._emit('// ' + line) 159 | continue; 160 | m = match_pragma(line) 161 | if m: 162 | self._prep_pragma(line, spath) 163 | else: 164 | self._emit(line) 165 | 166 | def _prep_pragma(self, line, spath): 167 | m = match_use(line) 168 | if m: 169 | self._prep_use(line, m, spath) 170 | else: 171 | m = match_map(line) 172 | if m: 173 | self._prep_map(line, spath) 174 | else: 175 | self._emit(line) 176 | 177 | def _prep_use(self, line, m, spath): 178 | use_path = spath.parent / m.group('file') 179 | with use_path.open() as f: 180 | self._prep_stream(f, use_path) 181 | 182 | def _prep_map(self, line, spath): 183 | m = match_image(line) 184 | if m: 185 | var = m.group('var') 186 | file = m.group('file') 187 | self.images.append(ImageInfo(var=var, file=file, spath=spath)) 188 | else: 189 | m = match_builtin(line) 190 | if m: 191 | var = m.group('var') 192 | predef = self._builtin_to_predef(m.group('builtin')) 193 | self.predefs.append(PredefInfo(var=var, predef=predef)) 194 | else: 195 | m = match_perip(line) 196 | if m: 197 | var = m.group('var') 198 | pd = PredefInfo(var=var, predef=Predefined.IMU) 199 | self.predefs.append(pd) 200 | else: 201 | msg = 'unknown map pragma: {}'.format(line.strip()) 202 | raise Exception(msg) 203 | 204 | def _builtin_to_predef(self, builtin): 205 | bsp = builtin.split() 206 | if bsp == 'RGBA Noise Small'.split(): 207 | return Predefined.NOISE_SMALL 208 | if bsp == 'RGBA Noise Medium'.split(): 209 | return Predefined.NOISE_MEDIUM 210 | if bsp == 'Back Buffer'.split(): 211 | return Predefined.BACK_BUFFER 212 | msg = 'unknown builtin: {}'.format(builtin) 213 | raise Exception(msg) 214 | 215 | def _post_preprocess(self): 216 | src = self.out.getvalue() 217 | idents = set(findall_idents(src)) 218 | prologue = '' 219 | for img in self.images: 220 | dcl = 'uniform sampler2D {};\n'.format(img.var) 221 | prologue += dcl; 222 | for pd in self.predefs: 223 | dcl = 'uniform sampler2D {};\n'.format(pd.var) 224 | prologue += dcl; 225 | for var in predefined_vars: 226 | if var.name in idents: 227 | size_dcl = '[{}]'.format(var.size) if var.size else '' 228 | dcl = 'uniform {} {}{};\n'.format(var.type, var.name, size_dcl) 229 | prologue += dcl 230 | self.predefs += [PredefInfo(var.name, var.predef)] 231 | if 'main' in idents: 232 | epilogue = '' 233 | elif 'mainImage' in idents: 234 | epilogue = image_main_source 235 | elif 'mainCube' in idents: 236 | epilogue = cube_main_source + image_main_source 237 | else: 238 | epilogue = '' # can't guess 239 | if prologue: 240 | prologue += '#line 1\n' 241 | src = prologue + src + epilogue 242 | self.out = io.StringIO(src) 243 | self.out.seek(0, io.SEEK_END) 244 | 245 | def _emit(self, line): 246 | self.out.write(line) 247 | 248 | 249 | def load_image(path): 250 | img = PIL.Image.open(path) 251 | mode = img.mode 252 | if mode != 'RGBA': 253 | exit('{} has unsupported mode {}'.format(path, mode)) 254 | a = np.array(img) 255 | return (namedtuple 256 | ('Image', 'width height data') 257 | (img.width, img.height, a.tobytes())) 258 | 259 | 260 | def load(fragment_shader_source, images, predefs): 261 | shade.init(LEDS_WIDTH, LEDS_HEIGHT) 262 | prog = Prog() 263 | prog.attach_shader(ShaderType.VERTEX, vertex_shader_source) 264 | prog.attach_shader(ShaderType.FRAGMENT, fragment_shader_source) 265 | for img_info in images: 266 | img_path = img_info.spath.parent / img_info.file 267 | img = load_image(img_path) 268 | prog.attach_image(img_info.var, img.width, img.height, img.data) 269 | for pd_info in predefs: 270 | prog.attach_predefined(pd_info.var, pd_info.predef) 271 | prog.check_okay() 272 | return prog 273 | 274 | def unload(): 275 | shade.deinit(); 276 | 277 | 278 | def run(prog, duration=None, fps=False): 279 | prog.make_current() 280 | if duration == None: 281 | duration = float('+inf') 282 | t0 = time.monotonic() 283 | shade.start() 284 | while True: 285 | now = time.monotonic() 286 | time_left = duration - (now - t0) 287 | if time_left <= 0: 288 | break 289 | sleep_time = 1 if fps else 1000 290 | if sleep_time > time_left: 291 | sleep_time = time_left 292 | time.sleep(sleep_time) 293 | if fps: 294 | print('FPS: {}'.format(shade.fps())) 295 | shade.stop() 296 | 297 | 298 | def shaderbox(file, expand=False, duration=None, fps=False): 299 | frag_shader = Preprocessor().process(file) 300 | if expand: 301 | print(frag_shader.source) 302 | exit() 303 | prog = load(frag_shader.source, frag_shader.images, frag_shader.predefs) 304 | try: 305 | run(prog, duration, fps) 306 | finally: 307 | unload() 308 | 309 | def main(argv): 310 | parser = ArgumentParser(description='Run GLSL shader on an LED cube.') 311 | parser.add_argument('-x', '--expand', action='store_true', 312 | help='expand shader source') 313 | parser.add_argument('-f', '--fps', action='store_true', 314 | help='periodically print frame rate') 315 | parser.add_argument('-d', '--duration', metavar='T', type=float, 316 | help='exit after T seconds') 317 | parser.add_argument('file', nargs='?', 318 | help='GLSL source file') 319 | args = parser.parse_args(argv[1:]) 320 | 321 | try: 322 | shaderbox(args.file, 323 | expand=args.expand, 324 | duration=args.duration, fps=args.fps) 325 | except Exception as x: 326 | exit(x) 327 | except KeyboardInterrupt: 328 | # This is the only way to get the process's exit status right. 329 | signal.signal(signal.SIGINT, signal.SIG_DFL) 330 | os.kill(os.getpid(), signal.SIGINT) 331 | 332 | if __name__ == '__main__': 333 | sys.exit(main(sys.argv)) 334 | -------------------------------------------------------------------------------- /python/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbob/shaderboy/2326c950609eb4ddb5d16b88df54ba4863042a4d/python/test.png -------------------------------------------------------------------------------- /scripts/conic: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Port of https://www.shadertoy.com/view/ttSGWh by wyatt 4 | 5 | 6 | import ctypes 7 | from ctypes import byref, c_char, c_char_p, c_double, c_int, c_void_p, POINTER 8 | import time 9 | 10 | vertex_shader_source = ''' 11 | attribute vec3 vert; 12 | 13 | void main(void) { 14 | gl_Position = vec4(vert, 1.0); 15 | } 16 | ''' 17 | 18 | fragment_shader_source = ''' 19 | uniform float iTime; 20 | uniform vec3 iResolution; 21 | #define R iResolution.xyz 22 | mat2 r (float a) { 23 | float s = sin(a), c = cos(a); 24 | return mat2(c,-s,s,c); 25 | } 26 | vec4 ro (vec3 U) { 27 | vec3 v = U; 28 | v.yz *= r (iTime); 29 | v.zx *= r (iTime); 30 | return vec4(v,(length(v.xy)-abs(v.z))); 31 | } 32 | void mainCube( out vec4 Q,vec3 U ) 33 | { 34 | // U = 2.*(U-0.33333*R)/R.y; 35 | U = 2. * U; 36 | 37 | vec4 38 | v = ro(U), 39 | n = ro(U+vec3(0.,1.,0.)/R), 40 | e = ro(U+vec3(1.,0.,0.)/R), 41 | s = ro(U-vec3(0.,1.,0.)/R), 42 | w = ro(U-vec3(1.,0.,0.)/R); 43 | vec3 g = normalize(vec3(e.w-w.w,n.w-s.w,.01)); 44 | float h = dot(g,ro(vec3(1.,0.,0.)).xyz); 45 | Q = vec4(0.0, 0.0, 0.0, 0.0); 46 | Q += .2*vec4(0.5+0.5*h*h*h)*smoothstep(0.,-0.1,v.w); 47 | Q.rb += smoothstep(.1,.05,abs(v.w)); 48 | Q += smoothstep(.05,0.025,abs(v.x)); 49 | Q += smoothstep(.05,0.025,abs(v.y)); 50 | Q += smoothstep(.05,0.025,abs(v.z)); 51 | Q.rg += smoothstep(.25,.2,length(v.xy-v.z*vec2(cos(3.*iTime),sin(3.*iTime)))); 52 | Q.gb += smoothstep(.25,.2,length(v.xy-v.z*vec2(-cos(3.*iTime),-sin(3.*iTime)))); 53 | Q *= exp(-length(U)); 54 | } 55 | 56 | // Maps 2D output image space to 3D cube space. 57 | // 58 | // The returned coordinates are in the range of (-.5, .5). 59 | vec3 cube_map_to_3d(vec2 pos) { 60 | vec3 p = vec3(0.0); 61 | if (pos.x < 128.0) { 62 | // top 63 | p = vec3(1.0 - pos.y / 128.0, 64 | 1.0, 65 | pos.x / 128.0); 66 | } else if (pos.x < 256.0) { 67 | // back 68 | p = vec3(1.0 - pos.y / 128.0, 69 | 1.0 - (pos.x - 128.0) / 128.0, 70 | 1.0); 71 | } else if (pos.x < 384.0) { 72 | // bottom 73 | p = vec3(1.0 - pos.y / 128.0, 74 | 0.0, 75 | 1.0 - (pos.x - 256.0) / 128.0); 76 | } else if (pos.x < 512.0) { 77 | // right 78 | p = vec3(1.0, 79 | 1.0 - pos.y / 128.0, 80 | 1.0 - (pos.x - 384.0) / 128.0); 81 | } else if (pos.x < 640.0) { 82 | // front 83 | p = vec3(1.0 - (pos.x - 512.0) / 128.0, 84 | 1.0 - pos.y / 128.0, 85 | 0.0); 86 | } else if (pos.x < 768.0) { 87 | // left 88 | p = vec3(0, 89 | 1.0 - pos.y / 128.0, 90 | (pos.x - 640.0) / 128.0); 91 | } 92 | return p - 0.5; 93 | } 94 | #ifndef _EMULATOR 95 | void mainImage(out vec4 fragColor, in vec2 fragCoord) { 96 | mainCube(fragColor, cube_map_to_3d(fragCoord)); 97 | } 98 | #endif 99 | void main() { 100 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 101 | mainImage(gl_FragColor, gl_FragCoord.xy); 102 | gl_FragColor.a = 1.0; 103 | } 104 | ''' 105 | 106 | shade = ctypes.cdll.LoadLibrary('./libshade.so') 107 | 108 | use_prog = shade.shd_use_prog 109 | use_prog.argtypes = (c_void_p, ) 110 | 111 | create_prog = shade.shd_create_prog 112 | create_prog.restype = c_void_p 113 | 114 | destroy_prog = shade.shd_destroy_prog 115 | destroy_prog.argtypes = (c_void_p, ) 116 | 117 | prog_is_okay = shade.shd_prog_is_okay 118 | prog_is_okay.argtypes = (c_void_p, POINTER(c_char_p)) 119 | 120 | prog_attach_shader = shade.shd_prog_attach_shader 121 | prog_attach_shader.argtypes = (c_void_p, c_int, c_char_p) 122 | 123 | prog_attach_image = shade.shd_prog_attach_image 124 | prog_attach_image.argtypes = (c_void_p, c_char_p, c_int, c_int, c_char_p) 125 | 126 | prog_attach_predefined = shade.shd_prog_attach_predefined 127 | prog_attach_predefined.argtypes = (c_void_p, c_char_p, c_int) 128 | 129 | fps = shade.shd_fps 130 | fps.argtypes = () 131 | fps.restype = c_double 132 | 133 | VERT = c_int.in_dll(shade, 'SHD_SHADER_VERTEX_VALUE') 134 | FRAG = c_int.in_dll(shade, 'SHD_SHADER_FRAGMENT_VALUE') 135 | 136 | RESOLUTION = c_int.in_dll(shade, 'SHD_PREDEFINED_RESOLUTION_VALUE') 137 | PLAY_TIME = c_int.in_dll(shade, 'SHD_PREDEFINED_PLAY_TIME_VALUE') 138 | NOISE_SMALL = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_SMALL_VALUE') 139 | NOISE_MEDIUM = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_MEDIUM_VALUE') 140 | 141 | LEDS_WIDTH = 6 * 64 142 | LEDS_HEIGHT = 64 143 | 144 | # img = load_image('test.png') 145 | 146 | shade.shd_init(LEDS_WIDTH, LEDS_HEIGHT) 147 | 148 | prog = create_prog() 149 | prog_attach_shader(prog, VERT, vertex_shader_source.encode('ascii')) 150 | prog_attach_shader(prog, FRAG, fragment_shader_source.encode('ascii')) 151 | prog_attach_predefined(prog, b'iResolution', RESOLUTION) 152 | prog_attach_predefined(prog, b'iTime', PLAY_TIME) 153 | # prog_attach_image(prog, b'noise_small', *img) 154 | info_log = c_char_p(); 155 | if not shade.shd_prog_is_okay(prog, byref(info_log)): 156 | print(info_log.value.decode('ascii')) 157 | exit() 158 | shade.shd_use_prog(prog) 159 | 160 | shade.shd_start() 161 | 162 | for i in range(60): 163 | try: 164 | time.sleep(1) 165 | print('FPS: {:.3}'.format(fps())) 166 | except KeyboardInterrupt: 167 | print() 168 | break 169 | 170 | shade.shd_stop() 171 | destroy_prog(prog) 172 | shade.shd_deinit() 173 | -------------------------------------------------------------------------------- /scripts/nyan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import ctypes 4 | from ctypes import byref, c_char, c_char_p, c_double, c_int, c_void_p, POINTER 5 | import numpy as np 6 | import PIL.Image 7 | import time 8 | 9 | vertex_shader_source = ''' 10 | attribute vec3 vert; 11 | 12 | void main(void) { 13 | gl_Position = vec4(vert, 1.0); 14 | } 15 | ''' 16 | 17 | fragment_shader_source = ''' 18 | //#pragma map nyanTex=image:../img/nyan.png 19 | //#pragma map noise=builtin:RGBA Noise Small 20 | uniform float iTime; 21 | uniform vec3 iResolution; 22 | uniform sampler2D nyanTex; 23 | uniform sampler2D noise; 24 | 25 | const float TAIL_STEP_SIZE = 0.05; 26 | const float TAIL_RADIUS = 0.2; 27 | const int TAIL_NUM_STEPS = 8; 28 | // #pragma use "../libcube.glsl" 29 | const float PI = 3.141592; 30 | const float EPSILON = 0.0001; 31 | 32 | // Maps 2D output image space to 3D cube space. 33 | // 34 | // The returned coordinates are in the range of (-.5, .5). 35 | vec3 cube_map_to_3d(vec2 pos) { 36 | vec3 p = vec3(0.0); 37 | if (pos.x < 128.0) { 38 | // top 39 | p = vec3(1.0 - pos.y / 128.0, 40 | 1.0, 41 | pos.x / 128.0); 42 | } else if (pos.x < 256.0) { 43 | // back 44 | p = vec3(1.0 - pos.y / 128.0, 45 | 1.0 - (pos.x - 128.0) / 128.0, 46 | 1.0); 47 | } else if (pos.x < 384.0) { 48 | // bottom 49 | p = vec3(1.0 - pos.y / 128.0, 50 | 0.0, 51 | 1.0 - (pos.x - 256.0) / 128.0); 52 | } else if (pos.x < 512.0) { 53 | // right 54 | p = vec3(1.0, 55 | 1.0 - pos.y / 128.0, 56 | 1.0 - (pos.x - 384.0) / 128.0); 57 | } else if (pos.x < 640.0) { 58 | // front 59 | p = vec3(1.0 - (pos.x - 512.0) / 128.0, 60 | 1.0 - pos.y / 128.0, 61 | 0.0); 62 | } else if (pos.x < 768.0) { 63 | // left 64 | p = vec3(0, 65 | 1.0 - pos.y / 128.0, 66 | (pos.x - 640.0) / 128.0); 67 | } 68 | return p - 0.5; 69 | } 70 | 71 | 72 | mat3 movement(float time) { 73 | float ry = time * 0.8; 74 | float rz = time * 2.1; 75 | mat3 my = mat3( 76 | cos(ry), 0.0, sin(ry), 77 | 0.0, 1.0, 0.0, 78 | -sin(ry), 0.0, cos(ry) 79 | ); 80 | mat3 mz = mat3( 81 | cos(rz), sin(rz), 0.0, 82 | -sin(rz), cos(rz), 0.0, 83 | 0.0, 0.0, 1.0 84 | ); 85 | return my * mz; 86 | } 87 | 88 | vec4 nyancatTexture(vec2 uv, float time) { 89 | uv = clamp(uv, 0.0, 1.0); 90 | const float numFrames = 5.0; 91 | const float subset = numFrames * 40.0 / 256.0; 92 | float idx = mod(floor(12.0 * time + 0.5), numFrames); 93 | return texture2D(nyanTex, vec2( 94 | subset * (idx + uv.x) / numFrames, 95 | uv.y 96 | )); 97 | } 98 | 99 | vec4 nyancat(vec3 fragCoord, float time) { 100 | vec3 side = fragCoord * movement(time); 101 | vec3 p = normalize(side); 102 | vec3 n = cross(p, vec3(1.0, 0.0, 0.0)); 103 | vec2 nyanUV = n.zy * vec2(-1.0, 1.0) * 0.7; 104 | return nyancatTexture(nyanUV + .5, iTime) * step(-side.x, 0.0); 105 | // return nyancatTexture(nyanUV + .5, 1.0 + 0. * iTime) * step(-side.x, 0.0); 106 | } 107 | 108 | vec4 tail(vec3 fragCoord, float time) { 109 | float tailDist = 1000000.; 110 | float tailIntensity = 0.0; 111 | for (int i = 0; i < TAIL_NUM_STEPS; i++) { 112 | vec3 ref = fragCoord * movement(time - float(i) * TAIL_STEP_SIZE); 113 | float dist = distance(normalize(ref), vec3(1.0, 0.0, 0.0)) * .6; 114 | tailDist = min(tailDist, dist); 115 | tailIntensity = min(tailIntensity + step(dist, TAIL_RADIUS), 1.0); 116 | } 117 | 118 | vec3 tailColors[6]; 119 | tailColors[0] = vec3(1.0, 0.0, 0.0); 120 | tailColors[1] = vec3(1.0, 0.6, 0.0); 121 | tailColors[2] = vec3(1.0, 1.0, 0.0); 122 | tailColors[3] = vec3(0.0, 1.0, 0.0); 123 | tailColors[4] = vec3(0.0, 0.5, 0.8); 124 | tailColors[5] = vec3(0.6, 0.0, 0.6); 125 | int i = int(floor(tailDist * 6.0 / TAIL_RADIUS)); 126 | vec3 c; 127 | if (i == 0) { 128 | c = tailColors[0]; 129 | } else if (i == 1) { 130 | c = tailColors[1]; 131 | } else if (i == 2) { 132 | c = tailColors[2]; 133 | } else if (i == 3) { 134 | c = tailColors[3]; 135 | } else if (i == 4) { 136 | c = tailColors[4]; 137 | } else if (i == 5) { 138 | c = tailColors[5]; 139 | } else { 140 | c = vec3(0); 141 | } 142 | return vec4(c, tailIntensity); 143 | } 144 | 145 | vec4 splarkles(vec3 fragCoord, float time) { 146 | const float speed = 2.2 - 2.0; 147 | const float thickness = .05; 148 | const float radius = .4; 149 | 150 | vec3 color = vec3(0); 151 | 152 | float a = 0.0; 153 | for (float i = 0.0; i < 16.0; i++) { 154 | float t = time * speed + texture2D(noise, vec2(i / 64., 0.0)).r; 155 | float n = mod(t, 1.0); 156 | 157 | vec4 r = (floor(t) + 10.0) * texture2D(noise, vec2(i / 64., 1.0)); 158 | mat3 mx = mat3( 159 | 1.0, 0.0, 0.0, 160 | 0.0, cos(r.x), sin(r.x), 161 | 0.0, -sin(r.x), cos(r.x) 162 | ); 163 | mat3 my = mat3( 164 | cos(r.y), 0.0, sin(r.y), 165 | 0.0, 1.0, 0.0, 166 | -sin(r.y), 0.0, cos(r.y) 167 | ); 168 | mat3 mz = mat3( 169 | cos(r.z), sin(r.z), 0.0, 170 | -sin(r.z), cos(r.z), 0.0, 171 | 0.0, 0.0, 1.0 172 | ); 173 | mat3 motion = mx * my * mz; 174 | 175 | vec3 p = normalize(fragCoord * motion); 176 | float dist = 1.0 - distance(vec3(0.0, 0.0, 1.0), p) + n * radius - thickness; 177 | vec3 direction = cross(p, vec3(0.0, 0.0, 1.0)); 178 | float angrad = atan(direction.x, direction.y) / PI; 179 | 180 | a += (1.0 - step(1.0, dist - thickness)) // ring, inner 181 | * step(1.0, dist) // ring, outer 182 | * step(0.5, mod(angrad * 8.0, 1.0)); // line splitting 183 | } 184 | 185 | return vec4(1., 1.0, 1.0, clamp(a, 0.0, 1.0)); 186 | } 187 | 188 | void mainCube(out vec4 fragColor, in vec3 fragCoord) { 189 | vec4 tail = tail(fragCoord, iTime); 190 | vec4 nyan = nyancat(fragCoord, iTime); 191 | // vec4 splarkles = splarkles(fragCoord, iTime); 192 | 193 | fragColor.rgb = vec3(0x07, 0x26, 0x47) / 255.0; 194 | fragColor.rgb = mix(fragColor.rgb, tail.rgb, tail.a); 195 | fragColor.rgb = mix(fragColor.rgb, nyan.rgb, nyan.a); 196 | // fragColor.rgb = mix(fragColor.rgb, splarkles.rgb, splarkles.a); 197 | } 198 | 199 | #ifndef _EMULATOR 200 | void mainImage(out vec4 fragColor, in vec2 fragCoord) { 201 | mainCube(fragColor, cube_map_to_3d(fragCoord)); 202 | } 203 | #endif 204 | 205 | void main() { 206 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 207 | mainImage(gl_FragColor, gl_FragCoord.xy); 208 | gl_FragColor.a = 1.0; 209 | } 210 | ''' 211 | 212 | def load_image(path): 213 | img = PIL.Image.open(path) 214 | mode = img.mode 215 | if mode != 'RGBA': 216 | exit('{} has unsupported mode {}'.format(path, mode)) 217 | a = np.array(img) 218 | return (img.width, img.height, a.tobytes()) 219 | 220 | shade = ctypes.cdll.LoadLibrary('./libshade.so') 221 | 222 | use_prog = shade.shd_use_prog 223 | use_prog.argtypes = (c_void_p, ) 224 | 225 | create_prog = shade.shd_create_prog 226 | create_prog.restype = c_void_p 227 | 228 | destroy_prog = shade.shd_destroy_prog 229 | destroy_prog.argtypes = (c_void_p, ) 230 | 231 | prog_is_okay = shade.shd_prog_is_okay 232 | prog_is_okay.argtypes = (c_void_p, POINTER(c_char_p)) 233 | 234 | prog_attach_shader = shade.shd_prog_attach_shader 235 | prog_attach_shader.argtypes = (c_void_p, c_int, c_char_p) 236 | 237 | prog_attach_image = shade.shd_prog_attach_image 238 | prog_attach_image.argtypes = (c_void_p, c_char_p, c_int, c_int, c_char_p) 239 | 240 | prog_attach_predefined = shade.shd_prog_attach_predefined 241 | prog_attach_predefined.argtypes = (c_void_p, c_char_p, c_int) 242 | 243 | fps = shade.shd_fps 244 | fps.argtypes = () 245 | fps.restype = c_double 246 | 247 | VERT = c_int.in_dll(shade, 'SHD_SHADER_VERTEX_VALUE') 248 | FRAG = c_int.in_dll(shade, 'SHD_SHADER_FRAGMENT_VALUE') 249 | 250 | RESOLUTION = c_int.in_dll(shade, 'SHD_PREDEFINED_RESOLUTION_VALUE') 251 | PLAY_TIME = c_int.in_dll(shade, 'SHD_PREDEFINED_PLAY_TIME_VALUE') 252 | NOISE_SMALL = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_SMALL_VALUE') 253 | NOISE_MEDIUM = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_MEDIUM_VALUE') 254 | 255 | LEDS_WIDTH = 6 * 64 256 | LEDS_HEIGHT = 64 257 | 258 | img = load_image('test.png') 259 | 260 | shade.shd_init(LEDS_WIDTH, LEDS_HEIGHT) 261 | 262 | prog = create_prog() 263 | prog_attach_shader(prog, VERT, vertex_shader_source.encode('ascii')) 264 | prog_attach_shader(prog, FRAG, fragment_shader_source.encode('ascii')) 265 | prog_attach_predefined(prog, b'iResolution', RESOLUTION) 266 | prog_attach_predefined(prog, b'iTime', PLAY_TIME) 267 | 268 | #pragma map nyanTex=image:../img/nyan.png 269 | #pragma map noise=builtin:RGBA Noise Small 270 | 271 | prog_attach_image(prog, b'nyanTex', *load_image('nyan.png')) 272 | prog_attach_predefined(prog, b'noise', NOISE_SMALL) 273 | info_log = c_char_p(); 274 | if not shade.shd_prog_is_okay(prog, byref(info_log)): 275 | print(info_log.value.decode('ascii')) 276 | exit() 277 | shade.shd_use_prog(prog) 278 | shade.shd_start() 279 | 280 | for i in range(30): 281 | try: 282 | time.sleep(1) 283 | print('FPS: {:.3}'.format(fps())) 284 | except KeyboardInterrupt: 285 | print() 286 | break 287 | 288 | shade.shd_stop() 289 | destroy_prog(prog) 290 | shade.shd_deinit() 291 | -------------------------------------------------------------------------------- /scripts/nyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbob/shaderboy/2326c950609eb4ddb5d16b88df54ba4863042a4d/scripts/nyan.png -------------------------------------------------------------------------------- /scripts/plasma: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import ctypes 4 | from ctypes import byref, c_char, c_char_p, c_double, c_int, c_void_p, POINTER 5 | import numpy as np 6 | import PIL.Image 7 | import time 8 | 9 | vertex_shader_source = ''' 10 | attribute vec3 vert; 11 | 12 | void main(void) { 13 | gl_Position = vec4(vert, 1.0); 14 | } 15 | ''' 16 | 17 | fragment_shader_source = ''' 18 | uniform float iTime; 19 | uniform vec3 iResolution; 20 | uniform sampler2D noise_small; 21 | 22 | int xor8(int a, int b) 23 | { 24 | int r = 0; 25 | 26 | if (a >= 512) 27 | a -= 512; 28 | if (b >= 512) 29 | b -= 512; 30 | if (a >= 256) 31 | a -= 256; 32 | if (b >= 256) 33 | b -= 256; 34 | 35 | if (a >= 128) { 36 | if (b < 128) { 37 | r += 128; 38 | } 39 | a -= 128; 40 | } else if (b >= 128) { 41 | r += 128; 42 | } 43 | if (b >= 128) { 44 | b -= 128; 45 | } 46 | 47 | if (a >= 64) { 48 | if (b < 64) 49 | r += 64; 50 | a -= 64; 51 | } else if (b >= 64) { 52 | r += 64; 53 | } 54 | if (b >= 64) { 55 | b -= 64; 56 | } 57 | 58 | if (a >= 32) { 59 | if (b < 32) 60 | r += 32; 61 | a -= 32; 62 | } else if (b >= 32) { 63 | r += 32; 64 | 65 | } 66 | if (b >= 32) { 67 | b -= 32; 68 | } 69 | 70 | if (a >= 16) { 71 | if (b < 16) 72 | r += 16; 73 | a -= 16; 74 | } else if (b >= 16) { 75 | r += 16; 76 | } 77 | if (b >= 16) { 78 | b -= 16; 79 | } 80 | 81 | if (a >= 8) { 82 | if (b < 8) 83 | r += 8; 84 | a -= 8; 85 | } else if (b >= 8) { 86 | r += 8; 87 | } 88 | if (b >= 8) { 89 | b -= 8; 90 | } 91 | 92 | if (a >= 4) { 93 | if (b < 4) 94 | r += 4; 95 | a -= 4; 96 | } else if (b >= 4) { 97 | r += 4; 98 | } 99 | if (b >= 4) { 100 | b -= 4; 101 | } 102 | 103 | if (a >= 2) { 104 | if (b < 2) 105 | r += 2; 106 | a -= 2; 107 | } else if (b >= 2) { 108 | r += 2; 109 | } 110 | if (b >= 2) { 111 | b -= 2; 112 | } 113 | 114 | if (a - b != 0) 115 | r += 1; 116 | 117 | return r; 118 | } 119 | 120 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 121 | { 122 | // Normalized pixel coordinates (from 0 to 1) 123 | vec2 uv = fragCoord/iResolution.xy; 124 | 125 | // Time varying pixel color 126 | vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4)); 127 | 128 | float px = 1.5*(fragCoord.x - 0.5 * iResolution.x) / iResolution.y; 129 | float py = 1.5*(fragCoord.y - 0.5 * iResolution.y) / iResolution.y; 130 | float r,g,b; 131 | 132 | px -= 0.75; 133 | py += 0.33; 134 | 135 | float px2 = px + sin(py * 4.6 + 0.5 * iTime) * 0.2; 136 | float py2 = py + sin(px * 2.6) * 0.7; 137 | 138 | px2 += sin(py2*px2 * 9.6) * 0.1; 139 | py2 += sin(px2*px2 * 7.6 + iTime) * 0.2; 140 | 141 | float fx = px2; 142 | float fy = py2; 143 | 144 | r = sin(10.0 * ((fx*fx)+(fy*fy))) * 0.5 + 0.5; 145 | g = sin(10.0 * sqrt((fx*fx)+(fy*fy))) * 0.5 + 0.5; 146 | b = sin(10.0 * pow((fx*fx)+(fy*fy), 1.5)) * 0.5 + 0.5; 147 | 148 | r = (r + b + g) / 3.0 + 0.2; 149 | r *= r; 150 | r *= r; 151 | g = (b + g) / 2.0; 152 | b = g; 153 | g *= 0.9; 154 | 155 | // r *= float((int(fragCoord.x) ^ int(fragCoord.y)) & 0xff) / 255.0; 156 | r *= float(xor8(int(fragCoord.x), int(fragCoord.y))) / 255.0; 157 | g += 0.2 * r; 158 | b += 0.2 * r; 159 | 160 | if ((px2 * 2.0) - floor(px2 * 2.0) < 0.5) { 161 | float t = r; 162 | r = g; 163 | g = t; 164 | } 165 | 166 | // Output to screen 167 | fragColor = vec4(r,g,b,1.0); 168 | } 169 | 170 | void main() { 171 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 172 | mainImage(gl_FragColor, gl_FragCoord.xy); 173 | gl_FragColor.a = 1.0; 174 | } 175 | ''' 176 | 177 | 178 | shade = ctypes.cdll.LoadLibrary('./libshade.so') 179 | 180 | use_prog = shade.shd_use_prog 181 | use_prog.argtypes = (c_void_p, ) 182 | 183 | create_prog = shade.shd_create_prog 184 | create_prog.restype = c_void_p 185 | 186 | destroy_prog = shade.shd_destroy_prog 187 | destroy_prog.argtypes = (c_void_p, ) 188 | 189 | prog_is_okay = shade.shd_prog_is_okay 190 | prog_is_okay.argtypes = (c_void_p, POINTER(c_char_p)) 191 | 192 | prog_attach_shader = shade.shd_prog_attach_shader 193 | prog_attach_shader.argtypes = (c_void_p, c_int, c_char_p) 194 | 195 | prog_attach_image = shade.shd_prog_attach_image 196 | prog_attach_image.argtypes = (c_void_p, c_char_p, c_int, c_int, c_char_p) 197 | 198 | prog_attach_predefined = shade.shd_prog_attach_predefined 199 | prog_attach_predefined.argtypes = (c_void_p, c_char_p, c_int) 200 | 201 | fps = shade.shd_fps 202 | fps.argtypes = () 203 | fps.restype = c_double 204 | 205 | VERT = c_int.in_dll(shade, 'SHD_SHADER_VERTEX_VALUE') 206 | FRAG = c_int.in_dll(shade, 'SHD_SHADER_FRAGMENT_VALUE') 207 | 208 | RESOLUTION = c_int.in_dll(shade, 'SHD_PREDEFINED_RESOLUTION_VALUE') 209 | PLAY_TIME = c_int.in_dll(shade, 'SHD_PREDEFINED_PLAY_TIME_VALUE') 210 | NOISE_SMALL = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_SMALL_VALUE') 211 | NOISE_MEDIUM = c_int.in_dll(shade, 'SHD_PREDEFINED_NOISE_MEDIUM_VALUE') 212 | 213 | LEDS_WIDTH = 6 * 64 214 | LEDS_HEIGHT = 64 215 | 216 | shade.shd_init(LEDS_WIDTH, LEDS_HEIGHT) 217 | 218 | prog = create_prog() 219 | prog_attach_shader(prog, VERT, vertex_shader_source.encode('ascii')) 220 | prog_attach_shader(prog, FRAG, fragment_shader_source.encode('ascii')) 221 | prog_attach_predefined(prog, b'iResolution', RESOLUTION) 222 | prog_attach_predefined(prog, b'iTime', PLAY_TIME) 223 | info_log = c_char_p(); 224 | if not shade.shd_prog_is_okay(prog, byref(info_log)): 225 | print(info_log.value.decode('ascii')) 226 | exit() 227 | shade.shd_use_prog(prog) 228 | 229 | shade.shd_start() 230 | 231 | for i in range(30): 232 | try: 233 | time.sleep(1) 234 | print('FPS: {:.3}'.format(fps())) 235 | except KeyboardInterrupt: 236 | print() 237 | break 238 | 239 | shade.shd_stop() 240 | destroy_prog(prog) 241 | shade.shd_deinit() 242 | -------------------------------------------------------------------------------- /scripts/smiley: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | import numpy as np 7 | import PIL.Image 8 | 9 | import shade 10 | 11 | 12 | LEDS_WIDTH = 6 * 64 13 | LEDS_HEIGHT = 64 14 | 15 | vertex_shader_source = ''' 16 | attribute vec3 vert; 17 | 18 | void main(void) { 19 | gl_Position = vec4(vert, 1.0); 20 | } 21 | ''' 22 | 23 | fragment_shader_source = ''' 24 | uniform float iTime; 25 | uniform vec3 iResolution; 26 | uniform sampler2D face; 27 | 28 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 29 | { 30 | // Normalized pixel coordinates (from 0 to 1) 31 | vec2 uv = fragCoord/iResolution.xy; 32 | vec2 uv6 = uv * vec2(6.0, 1.0); 33 | 34 | // Time varying pixel color 35 | vec3 col = 0.5 + 0.5*cos(iTime + uv6.xyx+vec3(0,2,4)); 36 | vec3 c2 = texture2D(face, uv6 + iTime * 0.1).rgb; 37 | c2 = c2 * c2; 38 | col *= c2; 39 | 40 | // Output to screen 41 | fragColor = vec4(col, 1.0); 42 | } 43 | 44 | void main() { 45 | gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); 46 | mainImage(gl_FragColor, gl_FragCoord.xy); 47 | gl_FragColor.a = 1.0; 48 | } 49 | ''' 50 | 51 | 52 | def load_image(path): 53 | img = PIL.Image.open(path) 54 | mode = img.mode 55 | if mode != 'RGBA': 56 | exit('{} has unsupported mode {}'.format(path, mode)) 57 | a = np.array(img) 58 | return (img.width, img.height, a.tobytes()) 59 | 60 | img = load_image('smiley.png') 61 | 62 | shade.init(LEDS_WIDTH, LEDS_HEIGHT) 63 | 64 | prog = shade.Prog() 65 | prog.attach_shader(shade.ShaderType.VERTEX, vertex_shader_source) 66 | prog.attach_shader(shade.ShaderType.FRAGMENT, fragment_shader_source) 67 | prog.attach_predefined('iResolution', shade.Predefined.RESOLUTION) 68 | prog.attach_predefined('iTime', shade.Predefined.PLAY_TIME) 69 | prog.attach_image('face', *img) 70 | prog.check_okay() 71 | prog.make_current() 72 | 73 | shade.start() 74 | 75 | for i in range(60): 76 | try: 77 | time.sleep(1) 78 | print('FPS: {:.3}'.format(shade.fps())) 79 | except KeyboardInterrupt: 80 | print() 81 | break 82 | 83 | shade.stop() 84 | 85 | del prog 86 | shade.deinit() 87 | -------------------------------------------------------------------------------- /scripts/smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbob/shaderboy/2326c950609eb4ddb5d16b88df54ba4863042a4d/scripts/smiley.png -------------------------------------------------------------------------------- /shaders/alphabet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbob/shaderboy/2326c950609eb4ddb5d16b88df54ba4863042a4d/shaders/alphabet.png -------------------------------------------------------------------------------- /shaders/alphabet6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbob/shaderboy/2326c950609eb4ddb5d16b88df54ba4863042a4d/shaders/alphabet6.png -------------------------------------------------------------------------------- /shaders/block.glsl: -------------------------------------------------------------------------------- 1 | # pragma map alphabet = image:alphabet6.png 2 | 3 | /* Return a permutation matrix whose first two columns are u and v basis 4 | vectors for a cube face, and whose third column indicates which axis 5 | (x,y,z) is maximal. */ 6 | mat3 getPT(in vec3 p) { 7 | vec3 a = abs(p); 8 | float c = max(max(a.x, a.y), a.z); 9 | 10 | vec3 s; 11 | if (c == a.x) 12 | s = vec3(1, 0, 0); 13 | else if (c == a.y) 14 | s = vec3(0, 1, 0); 15 | else 16 | s = vec3(0, 0, 1); 17 | s *= sign(dot(p, s)); 18 | 19 | vec3 q = s.yzx; 20 | return mat3(cross(q, s), q, s); 21 | } 22 | 23 | float axis_to_face(vec3 axis) { 24 | float idx = dot(abs(axis), vec3(0., 2., 4.)); 25 | if (dot(axis, vec3(1.)) < 0.) 26 | idx += 1.; 27 | return idx; 28 | } 29 | 30 | void mainCube(out vec4 fragColor, in vec3 fragCoord) 31 | { 32 | mat3 PTn = getPT(fragCoord); 33 | vec3 st = fragCoord * PTn; 34 | float f = axis_to_face(PTn[2]); 35 | 36 | vec2 st26 = vec2((st.x + 0.5 + f) / 6., st.y + 0.5); 37 | vec4 b = texture2D(alphabet, st26); 38 | vec3 face_col = abs(PTn[2]); 39 | face_col = min(vec3(0.6), face_col); // darken the colored parts. 40 | 41 | vec3 col = vec3(1.); 42 | col -= (1. - face_col) * b.r; 43 | col -= (1. - face_col) * b.g; 44 | 45 | fragColor.rgb = col.rgb; 46 | } 47 | -------------------------------------------------------------------------------- /shaders/conic.glsl: -------------------------------------------------------------------------------- 1 | #define R iResolution.xyz 2 | 3 | mat2 r (float a) { 4 | float s = sin(a), c = cos(a); 5 | return mat2(c,-s,s,c); 6 | } 7 | 8 | vec4 ro (vec3 U) { 9 | vec3 v = U; 10 | v.yz *= r (iTime); 11 | v.zx *= r (iTime); 12 | return vec4(v,(length(v.xy)-abs(v.z))); 13 | } 14 | 15 | void mainCube( out vec4 Q,vec3 U ) 16 | { 17 | // U = 2.*(U-0.33333*R)/R.y; 18 | U = 2. * U; 19 | 20 | vec4 21 | v = ro(U), 22 | n = ro(U+vec3(0.,1.,0.)/R), 23 | e = ro(U+vec3(1.,0.,0.)/R), 24 | s = ro(U-vec3(0.,1.,0.)/R), 25 | w = ro(U-vec3(1.,0.,0.)/R); 26 | vec3 g = normalize(vec3(e.w-w.w,n.w-s.w,.01)); 27 | float h = dot(g,ro(vec3(1.,0.,0.)).xyz); 28 | Q = vec4(0.0, 0.0, 0.0, 0.0); 29 | Q += .2*vec4(0.5+0.5*h*h*h)*smoothstep(0.,-0.1,v.w); 30 | Q.rb += smoothstep(.1,.05,abs(v.w)); 31 | Q += smoothstep(.05,0.025,abs(v.x)); 32 | Q += smoothstep(.05,0.025,abs(v.y)); 33 | Q += smoothstep(.05,0.025,abs(v.z)); 34 | Q.rg += smoothstep(.25,.2,length(v.xy-v.z*vec2(cos(3.*iTime),sin(3.*iTime)))); 35 | Q.gb += smoothstep(.25,.2,length(v.xy-v.z*vec2(-cos(3.*iTime),-sin(3.*iTime)))); 36 | Q *= exp(-length(U)); 37 | } 38 | -------------------------------------------------------------------------------- /shaders/curve-wobbler.glsl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shaderbox 2 | 3 | #define NCURVE 6 // number of curves 4 | #define NHARM 3 // number of harmonics 5 | #define ROLLOFF -0.8 // rolloff power - more negative is more rolloff 6 | #define SPEED 0.1 // overall animation speed 7 | #define EXCLUDE 0.1 // don't use a bit at the very center. 8 | #define THICK 0.03 9 | 10 | const float TAU = 6.283185307179586; 11 | const vec3 BLACK = vec3(0); 12 | const vec3 GRAY = vec3(0.5); 13 | const vec3 RED = vec3(1., 0., 0.); 14 | const vec3 BLUE = vec3(0., 0., 1.); 15 | 16 | struct params { 17 | float phase[NHARM]; 18 | float amp[NHARM]; 19 | float exp; 20 | float fade; 21 | }; 22 | 23 | float rand(float x) { 24 | return fract(100000.0 * sin(x)); 25 | } 26 | 27 | float vary(float t, float min, float max, float seed) { 28 | float freq = SPEED * rand(seed); 29 | float phase = rand(seed + 1.2); 30 | float v = sin(TAU * freq * (t + phase)); 31 | return min + (max - min) * (v + 1.) * 0.5; 32 | } 33 | 34 | void init(float t, int n, out params p) { 35 | float x = float(n + 1); 36 | for (int i = 0; i < NHARM; i++) { 37 | float h = float(i + 1); 38 | p.phase[i] = vary(t, 0., 1., x * h + 123.); 39 | p.amp[i] = vary(t, 0., pow(h, ROLLOFF), x * 234. + h * 345.); 40 | } 41 | p.exp = vary(t, 0.1, 5.0, rand(x + 456.)); 42 | p.fade = float(n) / float(NCURVE - 1); 43 | } 44 | 45 | float f(float x, const params p) { 46 | 47 | // sum harmonics. 48 | float y = 0.; 49 | float a = 0.; 50 | for (int i = 0; i < NHARM; i++) { 51 | float h = float(i + 1); 52 | y += p.amp[i] * sin(h * TAU * (x + p.phase[i])); 53 | a += p.amp[i]; 54 | } 55 | 56 | // scale to [0..1]. 57 | y = 0.5 / a * (y + a); 58 | 59 | // distort with exponent. 60 | y = pow(y, p.exp); 61 | return y; 62 | } 63 | 64 | vec3 fg_color(params p) { 65 | return mix(RED, BLUE, p.fade); 66 | } 67 | 68 | void facemap(in vec2 fragCoord, out vec2 uv) { 69 | uv = fragCoord/iResolution.xy; 70 | float x6 = 6. * uv.x; 71 | float face = floor(x6); 72 | vec2 fxy = vec2(1. + face - x6, 1. - uv.y); 73 | float side_face = 5. - face; 74 | 75 | // turn face 1 sideways. 76 | if (face == 1.0) { 77 | fxy = vec2(1. - fxy.y, fxy.x); 78 | side_face = 3.; 79 | } 80 | 81 | // make map for faces 0, 1, 2, . 82 | uv = vec2((side_face + fxy.x) / 4., abs(2. * fxy.y - 1.)); 83 | 84 | // create warp for faces 3 and 5. 85 | // a = angle widdershins from 7:30 o'clock. 86 | vec2 uvc = 2. * fxy - 1.; 87 | float a = mod(atan(uvc.y, uvc.x) / TAU + 0.375, 1.); 88 | if (face == 2.0) { 89 | // angle deasil from 4:30 o'clock 90 | a = mod(.25 - a, 1.); 91 | } 92 | 93 | float r0 = dot(uvc, uvc); 94 | float r1 = max(abs(uvc.x), abs(uvc.y)); 95 | float r = mix(r0, r1, r1); 96 | r = (1. + EXCLUDE) * r - EXCLUDE; 97 | if (face == 0. || face == 2.) { 98 | uv = vec2(a, r); 99 | } 100 | } 101 | 102 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 103 | { 104 | vec2 uv; 105 | facemap(fragCoord, uv); 106 | 107 | float x = uv.x; 108 | float y = uv.y; 109 | vec3 col = BLACK; 110 | for (int i = 0; i < NCURVE; i++) { 111 | params p; 112 | init(iTime, i, p); 113 | float fx = f(x, p); 114 | 115 | float brightness = smoothstep(THICK, 0.5 * THICK, abs(fx - uv.y)); 116 | col += mix(BLACK, fg_color(p), brightness); 117 | } 118 | 119 | // col.r = uv.x; 120 | // col.g = uv.y; 121 | 122 | // Output to screen 123 | fragColor = vec4(col,1.0); 124 | } 125 | -------------------------------------------------------------------------------- /shaders/filling-tiles-3D.glsl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shaderbox 2 | 3 | // Based on https://www.shadertoy.com/view/Xd3SW8 by Hanley 4 | 5 | #define TAU 6.283185307179586 6 | #define PI 3.141592653589793 7 | #define QTAU 1.5707963267948966 8 | 9 | float polygonDistanceField(in vec2 pixelPos, in int N) { 10 | // N = number of corners 11 | float a = atan(pixelPos.y, pixelPos.x) + QTAU; // angle 12 | float r = TAU / float(N); 13 | // shaping function that modulates the distances 14 | float distanceField = cos(floor(0.5 + a / r) * r - a) * length(pixelPos); 15 | return distanceField; 16 | } 17 | 18 | float minAngularDifference(in float angleA, in float angleB) { 19 | // Ensure input angles are -Ï€ to Ï€ 20 | angleA = mod(angleA, TAU); 21 | if (angleA>PI) angleA -= TAU; 22 | if (angleAPI) angleB -= TAU; 25 | if (angleB dist) { 41 | min_dist = dist; 42 | hue = h.z; 43 | } 44 | } 45 | } 46 | vec3 color = pal(fract(hue + 0.2 * iTime), A, B, C, D); 47 | // vec3 color = vec3(hue); 48 | color -= abs(sin(15. * min_dist)) * 0.2; 49 | color += 1. - step(0.02, min_dist); 50 | color.r += step(.99, f_st.x) + step(.99, f_st.y); 51 | 52 | fragColor.rgb = color; 53 | } 54 | -------------------------------------------------------------------------------- /shaders/munch0.glsl: -------------------------------------------------------------------------------- 1 | void decompose(in float c, out float bits[6]) 2 | { 3 | float c6 = c; 4 | float c5 = floor(c6 * 2.); 5 | float c4 = floor(c6 * 4.); 6 | float c3 = floor(c6 * 8.); 7 | float c2 = floor(c6 * 16.); 8 | float c1 = floor(c6 * 32.); 9 | float c0 = floor(c6 * 64.); 10 | c6 = floor(c6); 11 | 12 | bits[5] = c5 - 2. * c6; 13 | bits[4] = c4 - 2. * c5; 14 | bits[3] = c3 - 2. * c4; 15 | bits[2] = c2 - 2. * c3; 16 | bits[1] = c1 - 2. * c2; 17 | bits[0] = c0 - 2. * c1; 18 | } 19 | 20 | float compose(in float bits[6]) 21 | { 22 | return (bits[0] * (1. / 64.) + 23 | bits[1] * (1. / 32.) + 24 | bits[2] * (1. / 16.) + 25 | bits[3] * (1. / 8.) + 26 | bits[4] * (1. / 4.) + 27 | bits[5] * (1. / 2.)); 28 | } 29 | 30 | float xor(in float a, in float b) 31 | { 32 | float a_bits[6]; 33 | float b_bits[6]; 34 | float c_bits[6]; 35 | decompose(a, a_bits); 36 | decompose(b, b_bits); 37 | for (int i = 0; i < 6; i++) { 38 | c_bits[i] = a_bits[i] == b_bits[i] ? 0. : 1.; 39 | } 40 | return compose(c_bits); 41 | } 42 | 43 | float cond(float c) 44 | { 45 | return floor(c * 64.) / 64.; 46 | } 47 | 48 | void mainCube(out vec4 fragColor, in vec3 fragCoord) 49 | { 50 | // Normalized pixel coordinates (from 0 to 1) 51 | vec3 uvw = fragCoord + 0.5; 52 | float xx = xor(cond(uvw.x), xor(cond(uvw.y), cond(uvw.z))); 53 | float zz = mod(float(iFrame) / 64., 1.); 54 | 55 | // Time varying pixel color 56 | float r = abs(xx - zz); 57 | r = max(0., zz - xx); 58 | float g = max(0., xx - zz); 59 | 60 | vec3 col = vec3(abs(xx - zz) <= 1./64. ? 1. : 0.); 61 | col.r = r; 62 | col.g = g; 63 | 64 | // Output to screen 65 | fragColor = vec4(col, 1.0); 66 | } 67 | -------------------------------------------------------------------------------- /shaders/munch1.glsl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shaderbox 2 | 3 | mat3 xform(float time) 4 | { 5 | // return mat3(1.,0.,0.,0.,1.,0.,0.,0.,1); 6 | float rx = time * 0.13; 7 | float ry = time * 0.8; 8 | float rz = time * 0.3; 9 | mat3 mx = mat3( 10 | 1.0, 0.0, 0.0, 11 | 0.0, cos(rx), sin(rx), 12 | 0.0, -sin(rx), cos(rx) 13 | ); 14 | mat3 my = mat3( 15 | cos(ry), 0.0, sin(ry), 16 | 0.0, 1.0, 0.0, 17 | -sin(ry), 0.0, cos(ry) 18 | ); 19 | mat3 mz = mat3( 20 | cos(rz), sin(rz), 0.0, 21 | -sin(rz), cos(rz), 0.0, 22 | 0.0, 0.0, 1.0 23 | ); 24 | return mx * my * mz; 25 | } 26 | 27 | void decompose(in float c, out float bits[6]) 28 | { 29 | float c6 = c; 30 | float c5 = floor(c6 * 2.); 31 | float c4 = floor(c6 * 4.); 32 | float c3 = floor(c6 * 8.); 33 | float c2 = floor(c6 * 16.); 34 | float c1 = floor(c6 * 32.); 35 | float c0 = floor(c6 * 64.); 36 | c6 = floor(c6); 37 | 38 | bits[5] = c5 - 2. * c6; 39 | bits[4] = c4 - 2. * c5; 40 | bits[3] = c3 - 2. * c4; 41 | bits[2] = c2 - 2. * c3; 42 | bits[1] = c1 - 2. * c2; 43 | bits[0] = c0 - 2. * c1; 44 | } 45 | 46 | float compose(in float bits[6]) 47 | { 48 | return (bits[0] * (1. / 64.) + 49 | bits[1] * (1. / 32.) + 50 | bits[2] * (1. / 16.) + 51 | bits[3] * (1. / 8.) + 52 | bits[4] * (1. / 4.) + 53 | bits[5] * (1. / 2.)); 54 | } 55 | 56 | float xor(in float a, in float b) 57 | { 58 | float a_bits[6]; 59 | float b_bits[6]; 60 | float c_bits[6]; 61 | decompose(a, a_bits); 62 | decompose(b, b_bits); 63 | for (int i = 0; i < 6; i++) { 64 | c_bits[i] = a_bits[i] == b_bits[i] ? 0. : 1.; 65 | } 66 | return compose(c_bits); 67 | } 68 | 69 | float cond(float c) 70 | { 71 | return floor(mod(c * 64., 64.)) / 64.; 72 | } 73 | 74 | void mainCube(out vec4 fragColor, in vec3 fragCoord) 75 | { 76 | // Normalized pixel coordinates (from 0 to 1) 77 | 78 | vec3 uvw = fragCoord * xform(iTime); 79 | float xx = xor(cond(uvw.x), xor(cond(uvw.y), cond(uvw.z))); 80 | float zz = mod(float(iFrame / 2) / 64., 1.); 81 | 82 | float adxz = abs(xx - zz); 83 | bool mun = abs(xx - zz) <= 1./64.; 84 | float zs = 2. * abs(zz - 0.5); 85 | 86 | // Time varying pixel color 87 | float r = abs(xx - zs); 88 | float g = 0.5 - r; 89 | g *= smoothstep(0.0, 0.05, adxz); 90 | float b = mun ? 1. : 0.; 91 | 92 | vec3 col = vec3(0); 93 | // col.r = b * 0.4; 94 | col.g = g; 95 | col.b = b; 96 | col = max(vec3(0.01), col); // replace black with dark grey. 97 | 98 | // Output to screen 99 | fragColor = vec4(col, 1.0); 100 | } 101 | -------------------------------------------------------------------------------- /shaders/rgb.glsl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shaderbox 2 | 3 | void mainCube(out vec4 fragColor, in vec3 fragCoord) { 4 | fragColor.rgb = fragCoord.xyz + .5; 5 | } 6 | -------------------------------------------------------------------------------- /shaders/smiley.glsl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shaderbox 2 | 3 | #pragma map face=image:smiley.png 4 | 5 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 6 | { 7 | // Normalized pixel coordinates (from 0 to 1) 8 | vec2 uv = fragCoord/iResolution.xy; 9 | vec2 uv6 = uv * vec2(6.0, 1.0); 10 | 11 | // Time varying pixel color 12 | vec3 col = 0.5 + 0.5 * cos(iTime + uv6.xyx + vec3(0.0, 2.0, 4.0)); 13 | vec3 c2 = texture2D(face, uv6 + iTime * 0.1).rgb; 14 | c2 = c2 * c2; 15 | col *= c2; 16 | 17 | // Output to screen 18 | fragColor = vec4(col, 1.0); 19 | } 20 | -------------------------------------------------------------------------------- /shaders/smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbob/shaderboy/2326c950609eb4ddb5d16b88df54ba4863042a4d/shaders/smiley.png -------------------------------------------------------------------------------- /shaders/smooth-voronoi-contours.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Smooth Voronoi Contours 4 | ----------------------- 5 | 6 | Using a numerical gradient to produce smooth "fract" contours on 2D Voronoi. 7 | 8 | Shadertoy user "drone1" was kind enough to help me problem shoot some AA code 9 | yesterday on an image similar to this one, but I wanted to produce it without 10 | AA for realtime usage. There might be better methods, but this is the one I 11 | chose. It's partly based off of IQ's "Ellipse - Distance Estimation" example. 12 | 13 | If you press pause, you should notice that the contour lines are smooth and 14 | precise, regardless of the shape of the curve. 15 | 16 | For anyone wondering, the weird abstract image is just an amalgamation of two 17 | layers of smooth 2D Voronoi and an old concentric circle trick. In pseudo code: 18 | 19 | float val = Vor(p*freq)*A1 + Vor(p*freq*3.)*A2; 20 | val = clamp(cos(val*freq2*PI)*contrast, 0., 1.); 21 | 22 | See IQ's distance estimation example for a good explanation regarding the 23 | gradient related contour snippet: 24 | 25 | Ellipse - Distance Estimation - https://www.shadertoy.com/view/MdfGWn 26 | There's an accompanying articles, which is really insightful here: 27 | http://www.iquilezles.org/www/articles/distance/distance.htm 28 | 29 | Another example using the technique. 30 | 2D Noise Contours - Shane 31 | https://www.shadertoy.com/view/XdcGzB 32 | 33 | */ 34 | 35 | // Glossy version. It's there to show that the method works with raised surfaces too. 36 | //#define GLOSSY 37 | 38 | // Standard 2x2 hash algorithm. 39 | vec2 hash22(vec2 p) { 40 | 41 | // Faster, but probaly doesn't disperse things as nicely as other methods. 42 | float n = sin(dot(p, vec2(41, 289))); 43 | p = fract(vec2(2097152, 262144)*n); 44 | return cos(p*6.283 + iTime)*.5; 45 | //return abs(fract(p+ iTime*.25)-.5)*2. - .5; // Snooker. 46 | //return abs(cos(p*6.283 + iTime))*.5; // Bounce. 47 | 48 | } 49 | 50 | // Smooth Voronoi. I'm not sure who came up with the original, but I think IQ 51 | // was behind this particular algorithm. It's just like the regular Voronoi 52 | // algorithm, but instead of determining the minimum distance, you accumulate 53 | // values - analogous to adding metaball field values. The result is a nice 54 | // smooth pattern. The "falloff" variable is a smoothing factor of sorts. 55 | // 56 | float smoothVoronoi(vec2 p, float falloff) { 57 | 58 | vec2 ip = floor(p); p -= ip; 59 | 60 | float d = 1., res = 0.0; 61 | 62 | for(int i = -1; i <= 2; i++) { 63 | for(int j = -1; j <= 2; j++) { 64 | 65 | vec2 b = vec2(i, j); 66 | 67 | vec2 v = b - p + hash22(ip + b); 68 | 69 | d = max(dot(v,v), 1e-4); 70 | 71 | res += 1.0/pow( d, falloff ); 72 | } 73 | } 74 | 75 | return pow( 1./res, .5/falloff ); 76 | } 77 | 78 | // 2D function we'll be producing the contours for. 79 | float func2D(vec2 p){ 80 | 81 | 82 | float d = smoothVoronoi(p*2., 4.)*.66 + smoothVoronoi(p*6., 4.)*.34; 83 | 84 | return sqrt(d); 85 | 86 | } 87 | 88 | // Smooth fract function. A bit hacky, but it works. Handy for all kinds of things. 89 | // The final value controls the smoothing, so to speak. Common sense dictates that 90 | // tighter curves, require more blur, and straighter curves require less. The way 91 | // you do that is by passing in the function's curve-related value, which in this case 92 | // will be the function value divided by the length of the function's gradient. 93 | // 94 | // IQ's distance estimation example will give you more details: 95 | // Ellipse - Distance Estimation - https://www.shadertoy.com/view/MdfGWn 96 | // There's an accompanying article, which is really insightful, here: 97 | // http://www.iquilezles.org/www/articles/distance/distance.htm 98 | float smoothFract(float x, float sf){ 99 | 100 | x = fract(x); return min(x, x*(1.-x)*sf); 101 | 102 | } 103 | 104 | 105 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 106 | { 107 | // Screen coordinates. 108 | vec2 uv = (fragCoord.xy-iResolution.xy*.5) / iResolution.y; 109 | 110 | // Standard epsilon, used to determine the numerical gradient. 111 | vec2 e = vec2(0.001, 0); 112 | 113 | // The 2D function value. In this case, it's a couple of layers of 2D simplex-like noise. 114 | // In theory, any function should work. 115 | float f = func2D(uv); // Range [0, 1] 116 | 117 | // Length of the numerical gradient of the function above. Pretty standard. Requires two extra function 118 | // calls, which isn't too bad. 119 | float g = length( vec2(f - func2D(uv-e.xy), f - func2D(uv-e.yx)) )/(e.x); 120 | 121 | // Dividing a constant by the length of its gradient. Not quite the same, but related to IQ's 122 | // distance estimation example: Ellipse - Distance Estimation - https://www.shadertoy.com/view/MdfGWn 123 | g = 1./max(g, 0.001); 124 | 125 | // This is the crux of the shader. Taking a function value and producing some contours. In this case, 126 | // there are twelve. If you don't care about aliasing, it's as simple as: c = fract(f*12.); 127 | // If you do, and who wouldn't, you can use the following method. For a quick explanation, refer to the 128 | // "smoothFract" function or look up a concetric circle (bullseye) function. 129 | // 130 | // For a very good explanation, see IQ's distance estimation example: 131 | // Ellipse - Distance Estimation - https://www.shadertoy.com/view/MdfGWn 132 | // 133 | // There's an accompanying articles, which is really insightful, here: 134 | // http://www.iquilezles.org/www/articles/distance/distance.htm 135 | // 136 | float freq = 12.; 137 | // Smoothing factor. Hand picked. Ties in with the frequency above. Higher frequencies 138 | // require a lower value, and vice versa. 139 | float smoothFactor = iResolution.y*0.0125; 140 | 141 | #ifdef GLOSSY 142 | float c = smoothFract(f*freq, g*iResolution.y/16.); // Range [0, 1] 143 | //float c = fract(f*freq); // Aliased version, for comparison. 144 | #else 145 | float c = clamp(cos(f*freq*3.14159*2.)*g*smoothFactor, 0., 1.); // Range [0, 1] 146 | //float c = clamp(cos(f*freq*3.14159*2.)*2., 0., 1.); // Blurry contours, for comparison. 147 | #endif 148 | 149 | 150 | // Coloring. 151 | // 152 | // Convert "c" above to the greyscale and green colors. 153 | vec3 col = vec3(c); 154 | vec3 col2 = vec3(c*0.64, c, c*c*0.1); 155 | 156 | #ifdef GLOSSY 157 | col = mix(col, col2, -uv.y + clamp(fract(f*freq*0.5)*2.-1., 0., 1.0)); 158 | #else 159 | col = mix(col, col2, -uv.y + clamp(cos(f*freq*3.14159)*2., 0., 1.0)); 160 | #endif 161 | 162 | // Color in a couple of thecontours above. Not madatory, but it's pretty simple, and an interesting 163 | // way to pretty up functions. I use it all the time. 164 | f = f*freq; 165 | 166 | #ifdef GLOSSY 167 | if(f>8. && f<9.) col *= vec3(1, 0, .1); 168 | #else 169 | if(f>8.5 && f<9.5) col *= vec3(1, 0, .1); 170 | #endif 171 | 172 | 173 | // Since we have the gradient related value, we may as well use it for something. In this case, we're 174 | // adding a bit of highlighting. It's calculated for the contourless noise, so doesn't match up perfectly, 175 | // but it's good enough. Comment it out to see the texture on its own. 176 | #ifdef GLOSSY 177 | col += g*g*g*vec3(.3, .5, 1)*.25*.25*.25*.1; 178 | #endif 179 | 180 | 181 | //col = c * vec3(g*.25); // Just the function and gradient. Has a plastic wrap feel. 182 | 183 | // Done. 184 | fragColor = vec4( sqrt(clamp(col, 0., 1.)), 1.0 ); 185 | 186 | } 187 | -------------------------------------------------------------------------------- /shaders/v.glsl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shaderbox 2 | 3 | #define TAU 6.283185307179586 4 | 5 | #define A vec3(0.5, 0.5, 0.5) 6 | #define B vec3(0.5, 0.5, 0.5) 7 | #define C vec3(1.0, 0.7, 0.4) 8 | #define D vec3(0.0, 0.15, 0.20) 9 | 10 | vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) 11 | { 12 | return a + b * cos(TAU * (c * t + d)); 13 | } 14 | 15 | vec3 hash32(vec2 p) 16 | { 17 | // Dave Hoskins CC BY-SA 4.0 18 | vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); 19 | p3 += dot(p3, p3.yxz+19.19); 20 | return fract((p3.xxy+p3.yzz)*p3.zyx); 21 | } 22 | 23 | void mainImage(out vec4 fragColor, in vec2 fragCoord) { 24 | vec2 divisions = vec2(24, 4); 25 | vec2 st = divisions * fragCoord / iResolution.xy; 26 | 27 | vec2 i_st = floor(st); 28 | vec2 f_st = mod(st, 1.0); 29 | 30 | float min_dist = 1000.0; 31 | float hue; 32 | 33 | for (int i = -1; i <= +1; i++) { 34 | for (int j = -1; j <= +1; j++) { 35 | vec2 neighbor = vec2(i, j); 36 | vec3 h = hash32(i_st + neighbor); 37 | vec2 center = h.xy;; 38 | center = 0.5 + 0.4 * sin(iTime + 6.2831 * center); 39 | float dist = distance(f_st, neighbor + center); 40 | if (min_dist > dist) { 41 | min_dist = dist; 42 | hue = h.z; 43 | } 44 | } 45 | } 46 | vec3 color = pal(fract(hue + 0.2 * iTime), A, B, C, D); 47 | // vec3 color = vec3(hue); 48 | color -= abs(sin(15. * min_dist)) * 0.2; 49 | color += 1. - step(0.02, min_dist); 50 | // color.r += step(.99, f_st.x) + step(.99, f_st.y); 51 | 52 | fragColor.rgb = color; 53 | } 54 | -------------------------------------------------------------------------------- /shaders/voronoi.glsl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shaderbox 2 | 3 | #define TAU 6.283185307179586 4 | 5 | #define A vec3(0.5, 0.5, 0.5) 6 | #define B vec3(0.5, 0.5, 0.5) 7 | #define C vec3(1.0, 0.7, 0.4) 8 | #define D vec3(0.0, 0.15, 0.20) 9 | 10 | vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) 11 | { 12 | return a + b * cos(TAU * (c * t + d)); 13 | } 14 | 15 | vec3 hash32(vec2 p) 16 | { 17 | // Dave Hoskins CC BY-SA 4.0 18 | vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); 19 | p3 += dot(p3, p3.yxz+19.19); 20 | return fract((p3.xxy+p3.yzz)*p3.zyx); 21 | } 22 | 23 | void mainImage(out vec4 fragColor, in vec2 fragCoord) { 24 | vec2 divisions = vec2(24, 4); 25 | vec2 st = divisions * fragCoord / iResolution.xy; 26 | 27 | vec2 i_st = floor(st); 28 | vec2 f_st = mod(st, 1.0); 29 | 30 | float min_dist = 1000.0; 31 | float hue; 32 | 33 | for (int i = -1; i <= +1; i++) { 34 | for (int j = -1; j <= +1; j++) { 35 | vec2 neighbor = vec2(i, j); 36 | vec3 h = hash32(i_st + neighbor); 37 | vec2 center = h.xy;; 38 | center = 0.5 + 0.4 * sin(iTime + 6.2831 * center); 39 | float dist = distance(f_st, neighbor + center); 40 | if (min_dist > dist) { 41 | min_dist = dist; 42 | hue = h.z; 43 | } 44 | } 45 | } 46 | vec3 color = pal(fract(hue + 0.2 * iTime), A, B, C, D); 47 | // vec3 color = vec3(hue); 48 | color -= abs(sin(15. * min_dist)) * 0.2; 49 | color += 1. - step(0.02, min_dist); 50 | // color.r += step(.99, f_st.x) + step(.99, f_st.y); 51 | 52 | fragColor.rgb = color; 53 | } 54 | -------------------------------------------------------------------------------- /test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbob/shaderboy/2326c950609eb4ddb5d16b88df54ba4863042a4d/test.png --------------------------------------------------------------------------------