npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

shader-reload

v2.0.1

Published

A few tools for GLSL shader reloading at runtime.

Downloads

21

Readme

shader-reload

experimental

This is an experimental interface for live shader reloading in ThreeJS, regl, and other WebGL frameworks. This means you can edit your GLSL shader files without re-starting your entire application state. Works with regular strings, template strings, and/or transforms like brfs and glslify. Handles errors with a client-side popup that disappears on subsequent reloads.

screenshot

See this tweet for a longer video.

You might also be interested in shader-reload-cli, a development server (drop-in replacement for budo) that supports live-reloading GLSL with glslify built-in.

The code here could probably be adapted to work with other environments, e.g. Webpack/Express.

Quick Start

A quick way to test this is with the CLI version of this module, shader-reload-cli. This is a simple development server to get you up and running. For advanced projects, you may choose to use another development tool.

From your project folder using [email protected] and [email protected] or higher:

npm install shader-reload-cli -g

Add a simple index.js script like this:

index.js

const shader = require('./foo.shader');

// Initial source
console.log(shader.vertex, shader.fragment);

shader.on('change', () => {
  // New source
  console.log('Shader updated:', shader.vertex, shader.fragment);
});

It requires a shader module (which must have a .shader.js extension) with the following syntax.

foo.shader.js

module.exports = require('shader-reload')({
  vertex: '... shader source string ...',
  fragment: '... shader source string ...'
});

Now you can start the development server and begin editing & developing your application. Saving the shader modules will trigger a 'change' event without a hard page reload, but saving any other modules will reload the page as usual.

# opens the browser to localhost:9966/
shader-reload-cli src/index.js --open

:bulb: Under the hood, the shader-reload-cli script is running budo with glslify, so you can pass other options like --dir and --port. You can also add glslify transforms like glslify-hex to your package.json and they will get picked up by shader-reload-cli.

Details

Shader Files (.shader.js)

You will need to separate your shader source into its own module, which must have the extension .shader.js and require the shader-reload function.

Pass statically analyzable GLSL source code to the function like this:

module.exports = require('shader-reload')({
  vertex: '... shader source string ...',
  fragment: '... shader source string ...'
});

The return value of the shader-reload function is a Shader object, which has the same vertex and fragment properties (which are mutated on file change). You can also attach a shader.on('change', fn) event to react to changes.

Here is an example with inline shader source, using template strings.

blue.shader.js

module.exports = require('shader-reload')({
  fragment: `
  void main () {
    gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
  }`,
  vertex: `
  void main () {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1.0);
  }`
});

Then your ThreeJS source might look like this:

main.js

const shader = require('./blue.shader');

const material = new THREE.ShaderMaterial({
  vertexShader: shader.vertex,
  fragmentShader: shader.fragment
});

shader.on('change', () => {
  // Mark shader for recompilation
  material.vertexShader = shader.vertex;
  material.fragmentShader = shader.fragment;
  material.needsUpdate = true;
});

const mesh = new THREE.Mesh(geometry, material);
...

The examples include a LiveShaderMaterial which is a bit more robust for large applications.

Development Tool

Other than the .shader.js modules, you also need to have this set up with your development tool. You have a few options:

  • Use shader-reload-cli, it already includes glslify and shader reloading out of the box
  • Attach shader reloading to budo, see this gist for instructions
  • Attach shader reloading to your existing development environment using WebSockets and broadcasting 'shader-reload' events to clients

Browserify Transform

If you are using shader-reload-cli, it already includes the transforms needed for shader reloading and glslify.

If you are using budo directly or your own browserify scripts, you will need to include a source transform, e.g. -t shader-reload/transform, or in options:

...
  browserify: {
    transform: [ 'shader-reload/transform' ]
  }

Use with glslify

The shader-reload-cli script already includes glslify support out of the box, so you can organize your shaders into their own files and require glsl modules from npm:

blue.shader.js

const glslify = require('glslify');
const path = require('path');

module.exports = require('shader-reload')({
  vertex: glslify(path.resolve(__dirname, 'blue.vert')),
  fragment: glslify(path.resolve(__dirname, 'blue.frag'))
});

If you are using budo directly or your own development server, make sure to include glslify as a source transform before the shader-reload transform.

:warning: Babel and ES6 import

Babel will replace import statements with code that isn't easy to statically analyze, causing problems with this module. Instead of using import for 'shader-reload', you should require() it.

The same goes for requiring glslify.

Production Bundling

During production or when publishing the source to a non-development environment (i.e. without WebSockets), simply omit the shader-reload transform. Shaders will not change after construction.

If you are using shader-reload-cli and looking for a final JavaScript file for your static site, you can use browserify:

# install browserify
npm i browserify --save-dev

# bundle your index, with glslify if you need it
npx browserify index.js -t glslify > bundle.js

Use with ThreeJS

This module includes two Three.js utility classes for convenience in the three folder, LiveShaderMaterial and LiveRawShaderMaterial.

Read more about it here.

API Doc

shader = require('reload-shader')(shaderSource)

Pass in a shaderSource with { vertex, fragment } strings, and the Shader emitter returned will contain the following:

shader.vertex   // the latest vertex source
shader.fragment // the latest fragment source
shader.version  // an integer, starts at 0, increased with each change
shader.on('touch', fn)  // file was touched by fs file watcher
shader.on('change', fn) // vertex or fragment source was changed

require('reload-shader/receiver').on('touch', fn)

require('reload-shader/receiver').on('change', fn)

This event is triggered after all shaders have been updated, allowing you to react to the event application-wide instead of on a per-shader basis.

Running from Source

Clone this repo and npm install, then npm run example-three (ThreeJS) or npm run example-regl (regl). Edit the files inside the example/shaders/ folder and the shader will update without reloading the page. Saving other frontend files will reload the page as usual, restarting the application state.

Why not Webpack/Parcel HMR?

In my experience, trying to apply Hot Module Replacement to an entire WebGL application leads to a lot of subtle issues because GL relies so heavily on state, GPU memory, performance, etc.

However, shaders are easy to "hot replace" since they are really just strings. I wanted a workflow that provides lightning fast GLSL reloads, works smoothly with glslify, and does not rely on a bundle-wide HMR solution (which would be overkill). This module also handles some special edge cases like handling shader errors with a client-side popup.

License

MIT, see LICENSE.md for details.