Skip to content

SEPL. Inspired by Shadertoy, but powered by Scheme for settings. Dynamically set textures, variables, and other logic that affects the execution.

License

Notifications You must be signed in to change notification settings

themkat/shade-eval-print-loop

Repository files navigation

https://github.com/themkat/shade-eval-print-loop/actions/workflows/build.yml/badge.svg

Shade Eval Print Loop (SEPL)

Note

This package is experimental, and will continue to evolve. Should be considered alpha quality software at the moment.

Live coding and prototyping environment for fragment shaders. Inspired by Shadertoy, but powered by Scheme for dynamically setting uniforms and other values. Dynamically set textures, variables, and other logic that affects the execution.

Emacs is the only supported editor from my end, but the program should be able to support all editors that can connect to a process through TCP inferior-lisp style.

./screenshot.gif (screenshot from an Emacs session where we see live reloading of shader, as well as error message printing)

Features

  • Live-reloading of fragment shaders. Useful for prototyping your materials, raymarching scenes and more.
  • Set uniforms and load textures with CPU side Scheme scripting.
  • Set uniforms that automatically update every 50 milliseconds (or close depending on your system specs). This can be elapsed time, or any other arbitrary code you may want to execute.
  • REPL (read-eval-print-loop) to interact with the shader runtime in Scheme.
  • Emacs mode using comint to easily interact with the running SEPL instance.

Why not just use Shadertoy? Shadertoy is good, but I want the power of Scheme and Emacs in here as well. Configure it the way I want to. Make the uniforms the names I want to, so I can easily plug the shader into other projects once I’m done.

Creators wishlist

Features I might want to introduce:

  • [ ] Noise textures (Perlin noise)
  • [ ] Keyboard listener functions.
  • [ ] Mouse listener functions. Maybe position and/or click? The issue here is how to NOT overload the event input channels. Sending every mouse position without any delays or logic will probably do just that.

Usage

Build

Nothing fancy.

cargo build --release

If this seems new to you: do you even Rust?

Emacs modes install

First, add the emacs directory to your load path. Then you can load sepl-mode:

(add-to-list 'load-path "/path/to/shade-eval-print-loop/emacs/")
(require 'sepl-mode)

Usage from Emacs

Connect to running SEPL process

You first need to start a SEPL process yourself. This is done by simply running the program compiled above with your fragment shader path as an argument.

Once it is started, you can connect to this process directly from Emacs with sepl-repl-connect. Any errors are printed on screen in the SEPL window, but the full error log and outputs can be found in the *SEPL-STDOUT* buffer.

Start SEPL process “automatically”

You first need to set the path to the SEPL executable. You will find it in your target/release/ directory. Example: /path/to/shade-eval-print-loop/target/release/shade-eval-print-loop.

(setq sepl-program-bin "/path/to/shade-eval-print-loop/target/release/shade-eval-print-loop")

Now simply open your fragment shader of choice! Or an empty one. Simply run the command M-x sepl-repl-start. A new window will start, which will be your fragment shader instance. Any errors are printed on screen, but the full error log and outputs can be found in the *SEPL-STDOUT* buffer. There is also a Scheme REPL in *SEPL REPL* which can be used to evaluate scheme code to make new uniforms, load textures and similar operations.

Interact with REPL from Scheme source files

You may have a scheme source file you wish to execute code from? Simply open your Scheme file and activate sepl-mode. You can now use C-x C-e to evaluate s-expression by s-expression, in any order you wish. Or evaluate the entire buffer with M-x sepl-eval-buffer. An example file is found in example/myscheme.scm.

Scheme function interface

The SEPL interface provides a few Scheme functions:

  • (screen-size): Get the screen size as a list of two numbers, width and height. (example: (cadr (screen-size)) to get height).
  • (matrix row1 row2 row3 row4): Creates a 4x4 matrix where each argument is a list of 4 numbers.
  • (load-texture filename): Loads a texture from file. This will be a standard RGBA texture in memory, represented by 4 bytes in GPU memory. Other texture types are planned. Maybe a simple list of numbers could have converter methods for 1D textures? (to be used as lookup tables or arbitrary writes on modern hardware?). Or maybe 3D textures could be fun somehow?
  • (set-uniform! name value): Sets the uniform with name (a string) to the value in value. SEPL will infer the type automatically. Floats, matrices and textures are currently supported.
  • (set-dynamic-uniform! name closure): Same as for set-uniform!, but the value is re-calculated every 50 milliseconds or so (depending on your computer). In this version the argument is a function that takes 0 arguments. You can do arbitrary logic inside of it, but it should not be too compute intensive. Then the program will slow down a lot. You might even make your computer unresponsive until you quit the program, like I did to my Macbook Air M1…
  • (delete-dynamic-uniform! name): Deletes a dynamic uniform. This can be used to free up CPU resources if you have added many uniforms with lots of calculations that are suddenly unused.

If you by any means are interested in this project, feel free to add wishlists in Issues <3

Architecture

SEPL starts a group of threads to do work in parallel. The main thread is the render thread, which handles rendering using OpenGL (through Glium). In addition, a new thread is spawned for the Scheme interpreter logic. All communication with the Scheme interpreter (from outside the SEPL binary) happens through TCP. You can only run one SEPL process at a time, so the port number to connect to is deterministic. It’s always 42069 (I’m an adult, lol).

In summary:

  • Main rendering thread
  • Scheme interpreter thread
    • TCP server thread
      • Client thread (one for each connecting client)

All communication between threads is done through channels. For evaluating Scheme code, the Steel interpreter is used. This interpreter cannot be moved between threads. Therefore, each client thread sends the Scheme code to be evaluated via a channel, and receives the reply in a channel. For simplicity, these are simply strings.

The Scheme code has information on the rendering context through channels as well. Updates on screen size (and hopefully soon: key presses and mouse positions) are done this way.

Contributing

Do you want to contribute (for some reason)? Great! Be kind and constructive, and it will be great to work together :) Feel free to send in PRs with smaller changes. If you have feature requests or want bigger changes, please start by making an issue. We then have a place to discuss it, and you may then work on it without too much wasted effort :)

About

SEPL. Inspired by Shadertoy, but powered by Scheme for settings. Dynamically set textures, variables, and other logic that affects the execution.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published